JAVA (13) - String과 불변성
1. 개요
자바에서 String은 가장 자주 사용되는 클래스 중 하나다. 다른 객체와 달리 String은 한번 생성되면 내부의 값을 변경할 수 없는 불변(Immutable) 객체로 설계되었다. 이러한 불변 특성은 자바의 메모리 관리 및 안정성 측면에서 매우 중요한 역할을 한다.
2. String의 리터럴 생성과 new 연산자
자바에서 문자열을 생성하는 방식은 크게 리터럴(Literal)을 사용하는 방식과 new 연산자를 사용하는 방식으로 나뉜다.
- 문자열 리터럴:
String str1 = "hello";와 같이 생성하면 힙 메모리 내의 String Constant Pool(문자열 상수 풀)에 객체가 생성된다. 이미 동일한 문자열이 풀에 존재한다면 기존 객체의 참조를 재사용한다. new연산자:String str2 = new String("hello");와 같이 생성하면 상수가 아닌 일반적인 힙 메모리 영역에 매번 새로운String객체를 할당한다.
1
2
3
4
5
6
7
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); // true (동일한 String Pool 객체 참조)
System.out.println(a == c); // false (서로 다른 힙 메모리 객체 참조)
3. String이 불변인 이유 (Immutable)
자바에서 String 내부 데이터는 private final char[] (Java 9 이후부터는 private final byte[]) 형태로 관리된다. 일단 생성된 String 객체의 내부 값은 변경할 수 없다. 이러한 불변성을 선택한 주요 이유는 다음과 같다.
(1) String Constant Pool을 통한 메모리 절약
문자열은 응용 프로그램 전체에서 빈번하게 사용된다. 문자열이 불변하기 때문에 자바는 동일한 내용의 문자열을 메모리상에 하나만 올려 두고 여러 변수가 이를 안전하게 공유할 수 있다.
(2) 스레드 안정성 (Thread-Safety)
불변 객체는 상태가 변경되지 않으므로 여러 스레드가 동시에 접근하더라도 값이 변할 위험이 없다. 별도의 동기화(Synchronization) 처리 없이도 안전하게 공유할 수 있다.
(3) 보안성 (Security)
네트워크 연결, 파일 경로, 데이터베이스 연결 URL 등 시스템 주요 정보가 String 형태로 전달되는 경우가 많다. 문자열이 가변(Mutable)하다면 매개변수로 전달받은 값이 중간에 악의적으로 변경될 수 있다. 불변성은 이러한 참조 변경 위험을 차단한다.
(4) HashCode 캐싱
String은 불변하므로 생성 시점에 계산된 hashCode 값이 절대 바뀌지 않는다. 따라서 HashMap, HashSet 등 해시 기반 자료구조의 키(Key)로 사용하기에 매우 적합하며 캐싱을 통해 빠르게 동작한다.
4. 문자열 연산과 StringBuilder / StringBuffer
String 객체의 값을 변경하는 것처럼 보이는 연산(예: + 연산자나 .concat())을 수행하면, 기존 객체의 내용이 바뀌는 것이 아니라 새로운 String 객체가 생성된다.
1
2
3
String str = "Hello";
str = str + " World"; // "Hello World"라는 새로운 String 객체를 생성하여 str에 할당
반복문 내부에서 문자열 연결 연산을 지속하면 수많은 불필요한 객체가 생성되어 메모리가 낭비되고 성능이 저하된다. 이러한 문제를 해결하기 위해 가변(Mutable) 특성을 지닌 StringBuilder와 StringBuffer를 사용한다.
StringBuilder: 단일 스레드 환경에서 빠른 성능을 제공한다.StringBuffer: 동기화를 지원하여 다중 스레드(Multi-thread) 환경에서 안전하다.
1
2
3
4
StringBuilder sb = new StringBuilder("Hello");
sb.append(" World"); // 기존 객체 내부 데이터를 직접 변경
System.out.println(sb.toString());
5. 정리
자바의 String은 불변 객체로 설계되어 메모리 절약, 스레드 안정성, 보안성, 해시 키 효율성을 제공한다. 문자열 재사용을 극대화하기 위해 String Constant Pool을 활용한다. 하지만 문자열 수정이 빈번하게 발생하는 로직에서는 문자열 연결 연산 대신 StringBuilder나 StringBuffer를 활용하여 메모리 낭비를 방지하는 것이 바람직하다.