예제로 배우는 자바 String Pool 이해하기
개요#
처음 String Pool을 볼 때 제일 헷갈렸던 건, 분명 같은 문자열처럼 보이는데 어떤 건 ==가 true가 되고 어떤 건 false가 된다는 점이었습니다.
new String()으로 만든 경우, 리터럴로 만든 경우, intern()을 붙인 경우가 섞이면 더 헷갈립니다. 그래서 예제로 하나씩 비교해보면서 String Pool과 intern()이 실제로 어떻게 동작하는지 정리해보게 됐습니다.
들어가기 전에#
// Case 1
String str1 = "Hello, World!";
String str2 = "Hello, World!";
String str3 = new String("Hello, World!");
String str4 = new String("Hello, World!");
System.out.println(str1 == str2);
System.out.println(str1 == str3);
System.out.println(str3 == str4);
// Case 2
str3 = str3.intern();
str4 = str4.intern();
System.out.println(str1 == str3);
System.out.println(str3 == str4);
// Case 3
String str5 = "Hello, ";
String str6 = "World!";
String str7 = "Hello, " + "World!";
String str8 = str5 + str6;
System.out.println(str7 == str8);
System.out.println(str7.equals(str8));어떤 결과가 나올지 한번 예상해보겠습니다.
1. 문자열 리터럴과 String Pool#
자바에서 문자열은 보통 두 가지 방식으로 생성되는데, 어떤 방식을 쓰느냐에 따라 참조 동작이 달라진다.
문자열 리터럴
리터럴 형태로 문자열을 생성하면 JVM은 String Pool이라는 메모리 영역에 해당 문자열을 저장한다. 동일한 리터럴은 한 번만 저장되므로 str1 == str2는 true.
new 연산자를 통한 문자열 생성
new 연산자를 쓰면 Heap 영역에 별도 객체가 만들어진다. str1 == str3은 false, str3 == str4도 false.
2. intern() 메서드로 String Pool 활용하기#
자바의 intern() 메서드는 해당 문자열이 이미 String Pool에 있으면 그 참조를 돌려주고, 없으면 Pool에 추가한 뒤 그 참조를 반환한다.
intern() 호출 후 str1 == str3은 true, str3 == str4도 true.
3. 컴파일 시점 vs 런타임 시점 문자열 결합#
컴파일 시점 결합
String str7 = "Hello, " + "World!";컴파일러가 상수 리터럴끼리의 결합을 최적화해서 "Hello, World!"로 미리 계산하고 String Pool에 저장한다.
런타임 시점 결합
String str5 = "Hello, ";
String str6 = "World!";
String str8 = str5 + str6;변수를 사용한 결합은 런타임에 수행되기 때문에 새 문자열 객체가 생성된다. str7 == str8은 false, str7.equals(str8)은 true.
4. 전체 예제 코드#
public class UsingConstantPool {
public static void main(String[] args) {
// Case 1
String str1 = "Hello, World!";
String str2 = "Hello, World!";
String str3 = new String("Hello, World!");
String str4 = new String("Hello, World!");
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str3 == str4); // false
// Case 2
str3 = str3.intern();
str4 = str4.intern();
System.out.println(str1 == str3); // true
System.out.println(str3 == str4); // true
// Case 3
String str5 = "Hello, ";
String str6 = "World!";
String str7 = "Hello, " + "World!";
String str8 = str5 + str6;
System.out.println(str7 == str8); // false
System.out.println(str7.equals(str8)); // true
}
}5. 정리#
결국 핵심은 문자열 값이 같아 보여도, JVM이 그 문자열을 어디에 어떻게 올렸는지에 따라 참조 비교 결과가 달라진다는 점이다. ==는 참조를 보고, equals()는 내용을 본다.
intern()은 Heap에 있는 문자열을 String Pool 기준으로 다시 연결해주는 역할이고, 컴파일 시점에 붙는 문자열과 런타임에 붙는 문자열은 비교 결과가 다르게 나온다. String Pool을 외우기보다, "이 문자열이 지금 Pool에 있는가, 새 객체인가"를 떠올릴 수 있으면 직접 해봤을 때 훨씬 덜 헷갈리더라고요. (JLS 3.10.5 - String Literals)