dev notes

예제로 배우는 자바 String Pool 이해하기

2025-01-205 min read
공유

개요#

처음 String Pool을 볼 때 제일 헷갈렸던 건, 분명 같은 문자열처럼 보이는데 어떤 건 ==true가 되고 어떤 건 false가 된다는 점이었습니다.

new String()으로 만든 경우, 리터럴로 만든 경우, intern()을 붙인 경우가 섞이면 더 헷갈립니다. 그래서 예제로 하나씩 비교해보면서 String Pool과 intern()이 실제로 어떻게 동작하는지 정리해보게 됐습니다.

들어가기 전에#

java
// 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#

자바에서 문자열은 보통 두 가지 방식으로 생성됩니다.

자바 문자열 생성 방식 — 리터럴 vs new 키워드

자바 문자열 생성 방식 — 리터럴 vs new 키워드

문자열 리터럴

리터럴 형태로 문자열을 생성하면, JVM은 String Pool이라는 메모리 영역에 해당 문자열을 저장합니다. 동일한 리터럴은 한 번만 저장되므로 str1 == str2는 true.

new 연산자를 통한 문자열 생성

new 연산자를 통해 Heap 영역에 별도의 객체가 생성됩니다. str1 == str3은 false, str3 == str4도 false.

2. intern() 메서드로 String Pool 활용하기#

자바의 intern() 메서드는 해당 문자열이 이미 String Pool에 존재하면 그 참조를 반환하고, 존재하지 않으면 String Pool에 추가한 후 그 참조를 반환합니다.

intern() 메서드를 통한 String Pool 참조 확인

intern() 메서드를 통한 String Pool 참조 확인

intern() 호출 후 str1 == str3은 true, str3 == str4도 true.

3. 컴파일 시점 vs 런타임 시점 문자열 결합#

컴파일 시점 결합

java
String str7 = "Hello, " + "World!";

컴파일러가 상수 리터럴끼리의 결합을 최적화하여 "Hello, World!"로 미리 계산 후 String Pool에 저장합니다.

런타임 시점 결합

java
String str5 = "Hello, ";
String str6 = "World!";
String str8 = str5 + str6;

변수들을 사용한 결합은 런타임에 수행되어 새로운 문자열 객체가 생성됩니다. str7 == str8은 false, str7.equals(str8)은 true.

변수를 이용한 문자열 결합 시 새 객체 생성

변수를 이용한 문자열 결합 시 새 객체 생성

4. 전체 예제 코드#

java
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)

Connected Notes