1. 불변 객체(Immutable Object)
자바 면접 단골 질문 중 하나인 String은 불변객체인가? 라는 질문이 있습니다. 저 역시 면접 보기전 많이 본 질문 중 하나이고 검색을 해봤지만, 대충 암기만 하고 넘겼던 기억이 나는군요.. 실제로 자바, 스프링을 쓸때도 String이 많이 나오기때문에 이번 기회에 공부해보려고 이 글을 작성했습니다!
불변객체
불변 객체(Immutable Object)는 객체의 상태가 한 번 설정되면 변경할 수 없는 객체를 말합니다. 즉, 불변 객체는 객체를 생성한 후 내부 상태를 변경할 수 없으므로, 객체가 생성된 후에는 그 상태가 절대로 변하지 않습니다.
불변 객체의 특징
- 상태 변경 불가: 객체가 생성된 후에는 그 상태가 변경될 수 없습니다.
- 안전한 멀티스레딩: 상태가 변하지 않으므로 여러 스레드에서 동시에 접근해도 문제가 발생하지 않습니다.
- 불변 객체의 복사본: 불변 객체는 복사된 객체도 동일한 상태를 유지합니다. 이를 통해 객체가 변경되지 않음을 보장합니다.
불변 객체의 예시 코드를 간단하게 살펴보겠습니다


Person 객체는 불변 객체이므로 생성 후에는 name과 age를 변경할 수 없습니다. setter 메서드가 없어서 객체의 상태를 변경할 수 없습니다(단순히 setter 메서드가 없다고 해서 불변이 되는것은 아닙니다. 하지만 final로 변수 선언을 했기때문에 setter는 만들수 없습니다). 이를 통해 객체의 상태는 한 번 설정되면 변경되지 않는 불변 객체로 만들 수 있습니다.
만약 객체의 값을 변경하게 될 경우 사이드 이펙트가 발생할수 있습니다. (사이드 이펙트란, 함수나 메서드가 실행될 때 예상하지 못한 외부 상태의 변경이 발생하는 상황을 말합니다.) 그래서 저희는 불변을 사용해서 사이드 이펙트를 차단 시킬수있습니다.
그럼 불변객체의 값은 어떻게 변경할수있지? 불변 객체는 값을 변경할 수 없습니다. 따라서 불변 객체의 값을 변경하고 싶다면 변경하고 싶은 값으로 새로운 불변 객체를 생성해야 합니다. 이렇게 하면 기존 변수들이 참조하는 값에는 영향을 주지 않습니다.
불변객체에 대해 더 알고싶다면 https://www.youtube.com/watch?v=EOGOJdBy2Rg&ab_channel=%EC%89%AC%EC%9A%B4%EC%BD%94%EB%93%9C 이 유튜브를 참고해보세요!
그래서 String이 왜 불변이지?
https://www.artima.com/articles/james-gosling-on-java-may-2001#part13
자바를 만든 제임스 고슬링의 인터뷰 내용에 따르면 크게 4가지 이유로 볼수있을거 같습니다.
- 보안
- 만약 String 객체가 가변적(mutable)이라면, 이 정보가 외부에서 변경될 가능성이 생길수 있어서 보안상으로 좋지 않습니다.
- ex) SQL injection
- 불변 객체에서의 해시코드 캐싱
- 불변 객체(immutable object)에서 해시코드 캐싱 기법이 특히 유용합니다. 불변 객체는 한 번 생성되면 내부 값이 변하지 않기 때문에, 한 번 계산한 해시코드를 재사용해도 안전합니다. 이 방식은 계산을 한 번만 수행하고 이후에는 계산된 값을 계속 사용하여 성능을 최적화할 수 있습니다.
- 재사용성
- String은 String Constant Pool을 사용하여 동일한 문자열 값을 공유합니다. 이로 인해 메모리를 절약하고, 성능을 향상시킬 수 있습니다.
- 예를 들어, “Hello”라는 문자열을 여러 번 사용한다고 가정할 때, 각 사용마다 새로운 String 객체를 생성하는 대신 String Constant Pool에서 동일한 객체를 참조하도록 합니다.
- String이 불변 객체이기 때문에, 동일한 값에 대해 객체가 중복 생성되지 않으며 효율적인 메모리 사용이 가능합니다.
String s1 = "hello"; // String Constant Pool에서 "hello"를 참조
String s2 = new String("hello"); // 힙에 새로운 String 객체가 생성되고 "hello"는 String Constant Pool에서 참조
- 동기화
- String의 불변성은 멀티스레드 환경에서 중요한 스레드 안전성(Thread-safety)을 제공합니다. 멀티스레드 환경에서 String을 안전하게 사용할 수 있는 이유는 불변 객체이기 때문입니다.
- String 객체는 한 번 생성되면 그 내부 값이 변경되지 않기 때문에, 여러 스레드가 동시에 접근하더라도 상태 변경이 일어나지 않고, 동기화 없이 사용할 수 있습니다.
- 가변 객체라면, 여러 스레드가 동시에 접근하여 값을 수정할 수 있기 때문에 동기화가 필요하지만, String은 불변 객체로서 이 문제를 자연스럽게 해결합니다.
(스레드에 대해 더 파고들면 훨씬 깊은 개념이 나올거같지만 이 글에서는 간단하게만 작성하고 넘어가보겠습니다..!)
String Constant Pool(문자열 상수 풀)은 자바에서 문자열 리터럴을 효율적으로 관리하기 위한 메모리 관리 기법입니다. 이 풀을 사용하면 동일한 문자열 리터럴이 여러 번 생성되는 것을 방지하고, 메모리와 성능을 절약할 수 있습니다.


2. String과 StringBuilder
불변인 String 클래스의 단점은 문자를 더하거나 변경할 때 마다 계속해서 새로운 객체를 생성해야 합니다. 문자를 자주 더하거나 변경해야 하는 상황이라면 더 많은 String 객체를 만들고, GC해야 합니다. 결과적으로 컴퓨터의 CPU, 메모리를 자원을 더 많이 사용하게 됩니다. 그리고 문자열의 크기가 클수록, 문자열을 더 자주 변경할수록 시스템의 자원을 더 많이 소모합니다.
이 문제를 해결하기 위해 StringBuilder가 존재합니다 -> StringBuilder는 가변 String입니다
String은 byte가 final이기 때문에 변경 불가능한 반면에

StringBuilder가 상속받는 AbstractStringBuilder는 변경 가능한 byte인걸 볼 수 있습니다

String 은 불변합니다. 즉, 한 번 생성되면 그 내용을 변경할 수 없습니다. 따라서 문자열에 변화를 주려고 할 때마다 새로운 String 객체가 생성되고, 기존 객체는 버려집니다. 이 과정에서 메모리와 처리 시간을 더 많이 소모하지만, StringBuilder 는 가변적이기때문에 하나의 StringBuilder 객체 안에서 문자열을 추가, 삭제, 수정할 수 있으며, 이때마다 새로운 객체를 생성하지 않습니다. 이로 인해 메모리 사용을 줄이고 성능을 향상시킬 수 있습니다.
StringBuilder 는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한(불변) String 으로 변환하는 것이 좋습니다.
String과 StringBuilder의 성능 차이는 아래 코드를 실행해보면 확실하게 알 수 있습니다!
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("Hello Java ");
}
long endTime = System.currentTimeMillis();
String result = sb.toString();
System.out.println("result = " + result);
System.out.println("time = " + (endTime - startTime) + "ms");
}
time = 4ms
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
String result = "";
for (int i = 0; i < 100000; i++) {
result += "Hello Java ";
}
long endTime = System.currentTimeMillis();
System.out.println("result = " + result);
System.out.println("time = " + (endTime - startTime) + "ms");
}
time = 6089ms
사실 자바는 문자열에 + 연산을 사용하면, 컴파일 전 StringBuilder 클래스를 만든 후 다시 문자열로 돌려준다고 합니다.
하지만 위 코드에서는 반복문 안에서 반복 횟수만큼 StringBuilder 객체를 생성하기때문에 엄청나게 오래걸리는걸 볼수 있습니다.
정리
String은 불변성 문자열 연산이 적고 변하지 않는 문자열을 자주 사용할 경우 멀티쓰레드 환경일때
StringBuilder는 가변성 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 동기화를 지원하지 않아, 단일 쓰레드이거나 동기화를 고려하지 않아도 되는 경우 속도면에선 StringBuffer 보다 성능이 좋습니다.
StringBuffer는 가변성 문자열의 추가, 수정, 삭제 등이 빈번히 발생하는 경우 동기화를 지원하여, 멀티 스레드 환경에서도 안전하게 동작합니다 (StringBuffer는 이 글에서 다루지 않았지만 따로 찾아보시면 좋을 거 같습니다!)
불변에 대해 공부하다보니 김영한 선생님께서 이런 말씀을 해주셨습니다. 바뀌어 버리면 안되는 객체가 바뀌면 큰 문제가 생길수있어서 바뀌면 안되는것은 꼭 불변으로 설계를 해야한다. 하지만 불변은 값이 변경이 안되기때문에 새로운 값을 추가하려면 새로운 객체를 만들어야합니다. 그래서 객체를 계속 생성해야한다는 단점도 있다고 합니다! 불변, String 만 조금 공부했는데 설계까지 생각하게 되니깐 어려우면서도 더 깊게 생각해보는 시간이 된거같네요. 확실히 글을 쓰다보면 모르는것도 더 찾아보게 되고 쓰면서도 머리에 들어오기때문에 좋은거같습니다 ㅎㅎ
회사생활하다 보니 느끼는게 작은 개념 하나를 배우면 이걸 실제로 어떻게 적용해야하는지, 왜 쓰는지를 자꾸 생각하게 되네요.. 사수님의 영향이 큰거같습니다! 저에게 늘 가르침을 주셔서 감사드립니다..
쓰레드, 동기화, 자바의 메모리등 모르는 개념이 아직 많지만 하나씩 공부하다보면 저도 언젠가 다른사람에게 쉽게 설명해줄수 있는 날이 올거라 믿습니다!
100점짜리 글이 되는 그날까지!
참고자료
2 Comments
대표 멘토 - 아키텍터
2024-11-20좋은 글 감사합니다. 잘써주셨네요. 내용 하나하나에 정성이 들어간 그런 느낌이었습니다.
이 글이 많은 분들에게 알려지면 좋을 거 같습니다. 그런 의미에서 아래의 링크를 참고하여 수정하면 좋을 거 같습니다.
https://www.nuschool.co.kr/%eb%88%84%ec%8a%a4%ec%bf%a8-%ea%b3%b5%ec%a7%80%ec%82%ac%ed%95%ad/%ea%b3%b5%ec%a7%80%ed%95%84%eb%8f%85-%eb%88%84%ec%8a%a4%ec%bf%a8-%ec%bb%a4%eb%ae%a4%eb%8b%88%ed%8b%b0%ec%97%90-%ea%b8%80-%ec%9e%91%ec%84%b1%ed%95%98%eb%8a%94-%eb%b2%95-seo-%ec%b5%9c%ec%a0%81%ed%99%94/
자신의 글을 채점하는 기능을 가진 플러그인을 탑재했는데요. 글을 작성하는데 도움이 될 겁니다. ^^
커뮤니티가 확장할 수 있도록 도움을 주셔서 고맙습니다.
대표 멘토 - 아키텍터
2024-11-20아! 이것때문에 댓글을 달았는데 깜빡했네요.
게시글을 작성했는데 게시글 리스트에 사진은 안나와서 당황하셨죠? ㅎㅎ
특성이미지 설정!! 꼭 해주세요 ㅎㅎ 제가 보내준 링크로 들어가셔서 참고하면 할 수 있을 겁니다.