개요
이번 글에서는 예제를 통해 자바의 String Pool과 intern() 메서드 개념을 자세히 알아봅니다.
문자열 리터럴과 new 연산자를 통한 객체 생성, 그리고 intern() 메서드 사용 시 String Pool 내의 객체 참조를 어떻게 변경하는지 살펴봅니다.
들어가기 전에
// 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
자바에서 문자열은 보통 두 가지 방식으로 생성됩니다.
문자열 리터럴
예를 들어,
String str1 = "Hello, World!";
String str2 = "Hello, World!";
위와 같이 리터럴 형태로 문자열을 생성하면, JVM은 String Pool이라는 메모리 영역에 해당 문자열을 저장합니다. 동일한 리터럴은 한 번만 저장되므로,
System.out.println(str1 == str2); // true
로 두 변수는 같은 객체를 참조하게 됩니다.
new 연산자를 통한 문자열 생성
예를 들어,
String str3 = new String("Hello, World!");
String str4 = new String("Hello, World!");
이 경우, 문자열 리터럴 "Hello, World!"는 String Pool에 존재하더라도, new 연산자를 통해 Heap 영역에 별도의 객체가 생성됩니다. 그래서
System.out.println(str1 == str3); // false
System.out.println(str3 == str4); // false
2. intern() 메서드로 String Pool 활용하기
자바의 intern() 메서드는 해당 문자열이 이미 String Pool에 존재하면 그 참조를 반환하고, 존재하지 않으면 String Pool에 추가한 후 그 참조를 반환합니다.
따라서, 아래와 같이 new 연산자로 생성한 문자열에 대해 intern()를 호출하면 String Pool에 저장된 문자열을 참조하게 되어
str3 = str3.intern();
str4 = str4.intern();
결과적으로,
System.out.println(str1 == str3); // true
System.out.println(str3 == str4); // true
3. 컴파일 시점 vs 런타임 시점 문자열 결합
문자열 결합은 두 방식으로 처리됩니다.
컴파일 시점 결합
String str7 = "Hello, " + "World!";
컴파일러가 상수 리터럴끼리의 결합을 최적화하여 "Hello, World!"로 미리 계산 후 String Pool에 저장합니다.
런타임 시점 결합
String str5 = "Hello, ";
String str6 = "World!";
String str8 = str5 + str6;
변수들을 사용한 결합은 런타임에 수행되어 새로운 문자열 객체가 생성됩니다. 이 객체는 기본적으로 String Pool에 저장되지 않으므로,
System.out.println(str7 == str8); // false
System.out.println(str7.equals(str8)); // true
equals() 을 사용한다면 역시나 같은 결과가 나오지만 str7은 String Pool에 있는 리터럴 참조이고, str8은 런타임에 만들어진 새로운 객체의 참조이기에 false 가 나온다.
4. UsingConstantPool 예제 코드
아래는 주석과 출력 결과 설명 없이 정리한 최종 코드입니다.
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
// str1은 String Pool의 객체이고, str3은 Heap에 새로 생성된 객체이므로 서로 다른 참조를 가진다. 따라서 false가 출력된다.
System.out.println(str1 == str3); // false
// 각각 별도의 new 연산자로 생성된 Heap 객체이므로 서로 다른 참조를 가진다. 따라서 false가 출력된다.
System.out.println(str3 == str4); // false
// Case 2
// intern() 메서드는 호출한 문자열 객체가 이미 String Pool에 존재하면 그 객체의 참조를 반환하고, 존재하지 않으면 현재 문자열 객체를 String Pool에 추가한 후 그 참조를 반환한다.
str3 = str3.intern();
str4 = str4.intern();
System.out.println(str1 == str3); // true
// str3과 str4 모두 "Hello, World!"라는 String Pool에 저장된 객체의 참조를 가지고 있으므로 true가 출력된다.
System.out.println(str3 == str4); // true
// Case 3
String str5 = "Hello, ";
String str6 = "World!";
// "Hello, " + "World!"는 두 리터럴의 연결이 컴파일 시점에 수행되어 "Hello, World!"라는 단일 리터럴로 변환
String str7 = "Hello, " + "World!";
// str5 + str6는 변수들을 사용한 연결이기 때문에 런타임에 문자열 결합이 수행됩니다. 즉, 새로운 문자열 객체가 생성되며, 이 객체는 기본적으로 String Pool에 자동으로 저장되지 않는다.
String str8 = str5 + str6;
// str7은 String Pool에 있는 리터럴 참조이고, str8은 런타임에 만들어진 새로운 객체의 참조이다.
System.out.println(str7 == str8); // false
// 두 문자열의 값(내용)이 동일하기 때문에 equals() 메서드는 true를 반환한다.
System.out.println(str7.equals(str8)); // true
}
}
5. 정리 및 마무리
String Pool은 JVM이 문자열 리터럴을 재사용하기 위해 관리하는 메모리 영역입니다.
==
연산자는 객체의 참조를 비교하고, equals()
메서드는 문자열의 내용을 비교합니다.
intern()
메서드를 사용하면 Heap 영역의 문자열 객체를 String Pool에 있는 동일한 값의 객체로 연결할 수 있습니다.
또한, 컴파일 시점에 결합되는 문자열 리터럴과 런타임에 결합되는 문자열 객체는 비교 결과가 다를 수 있으므로 주의해야 합니다.
'Java' 카테고리의 다른 글
예제로 배우는 자바가 배열을 생성하는 원리 이해하기 (0) | 2025.01.21 |
---|---|
Java 조각모음 [3] (0) | 2025.01.20 |
Java 조각모음 [2] (0) | 2025.01.17 |
Java 조각모음 [1] (1) | 2025.01.16 |
ArrayList/LinkedList 단순 순회시 For, Enhanced For , Iterator, ListIterator, Stream.forEach() 성능비교 (1) | 2024.08.27 |