String이 불변(immutable)이라는 것은 객체 자체를 변경할 수 없다는 의미입니다. 다만 객체에 대한 참조(reference)는 얼마든지 바꿀 수 있습니다. 한편 String을 final로 선언한 이유는 다른 클래스가 이를 상속해 불변성을 깨뜨리는 것을 막기 위함입니다.
String이 불변으로 설계된 주요 이유
- 보안(Security) — 네트워크 연결, 데이터베이스 접속 URL, 사용자 이름/비밀번호 등 민감한 파라미터는 대개 문자열로 표현됩니다. 만약 String이 가변(mutable)이라면 이러한 값들이 쉽게 변조될 수 있어 심각한 보안 문제를 일으킬 수 있습니다.
- 동기화와 동시성(Synchronization & Concurrency) — String이 불변이면 상태가 절대 변하지 않으므로 자동으로 스레드 안전(thread-safe)하게 되고, 멀티스레드 환경에서의 동기화 문제를 자연스럽게 해결합니다.
- 캐싱(Caching) — 컴파일러와 JVM은 값이 같은 문자열 리터럴을 하나의 객체로 공유하는 문자열 풀(String Pool)을 활용합니다. 예를 들어 a = "test", b = "test"라면 a와 b는 동일한 객체를 가리키므로 메모리를 절약하고 성능을 높일 수 있습니다. 불변성 덕분에 하나의 객체를 여러 참조가 공유해도 안전합니다.
- 클래스 로딩(Class Loading) — 클래스 로딩 시 문자열이 인수로 사용됩니다. String이 가변이라면 로딩 도중 값이 변경되어 엉뚱한 클래스가 로드되는 위험이 생길 수 있습니다.
예제 코드
public class StringImmutableDemo {
public static void main(String[] args) {
String st1 = "Tutorials";
String st2 = "Point";
System.out.println("The hashcode of st1 = " + st1.hashCode());
System.out.println("The hashcode of st2 = " + st2.hashCode());
st1 = st1 + st2;
System.out.println("The Hashcode after st1 is changed : "+ st1.hashCode());
}
}실행 결과
The hashcode of st1 = -594386763 The hashcode of st2 = 77292912 The Hashcode after st1 is changed : 962735579
위 예제에서 볼 수 있듯이 st1과 st2를 연결하면 기존 객체가 수정되는 것이 아니라 새로운 문자열 객체가 생성됩니다. 그 결과 해시코드가 완전히 달라진 것을 확인할 수 있으며, 이것이 String 불변성이 실제로 동작하는 방식입니다. 즉, 문자열에 어떤 연산을 하더라도 원본 객체는 그대로 유지되고 항상 새로운 객체가 반환됩니다.