String은 객체이다.
그럼 다른 객체들처럼 String 참조변수마다 heap영역에 새로운 공간을 할당받는 것일까?
아래는 나의 생각이다.
String str1 = "abced";
String str2 = "abced";
즉 abced 에 해당하는 값이 총 2개가 heap영역에 생성되고 따라서 참조변수는 heap영역의 메모리 주소를 가지므로 str1,str2가 가지고 있는 값이 서로 다를 것이라고 생각했다.
과연 내 생각이 맞을까?
String Literal은 소스 코드에서 문자열 값을 직접 표기하는 것으로
소스코드에서 읽혀지고, String 객체로 변환된다.
String str1 = "abced";
String str2 = "hello world";
String Literal은 heap 영역에 존재하는 String Constant Pool(상수 풀)에 저장된다.
동일한 문자열 리터럴은 하나의 객체만 생성하고 이를 공유한다.
따라서 같은 문자열 리터럴을 참조하는 여러 변수가 있다면, 이들은 같은 String 객체를 참조한다.
String Literal은 str1과 str2가 같은 메모리 주소를 참조하는 것을 확인할 수 있다.
public class StringTest {
/**
* 리터럴을 이용해 String 객체를 생성하면 Heap 영역 내부에 있는 String Constant Pool 에 저장된다.
*/
@DisplayName("String Literal로 생성한 두 객체는 값이 같다면 동일하다")
@Test
void stringLiteral() {
//String Constant Pool에 해당 값이 존재하지 않는다면 객체를 새로 생성한다.
String str1 = "abced";
String Constant Pool에 해당 값이 존재하므로 기존 reference를 참조한다.
String str2 = "abced";
Assertions.assertSame(str1,str2);
}
}
new 연산자로 String 객체를 생성하면 해당 객체는 heap 영역에 생성되고 참조 변수는 heap 영역의 메모리 주소값을 가진다.
동일한 값을 가진 String 객체를 생성한다고 해도, 각 String 객체를 만든다.
즉, 각 String 객체마다 다른 메모리 주소값을 가지고 있다.
따라서 new 연산자로 생성된 같은 값을 가진 String 객체가 있다고 하더라도, 이들은 다른 String 객체를 참조한다.
반대로 new 연산자를 이용해 String 객체를 생성하면 값은 같더라도 서로 다른 메모리 주소를 참조하는 것을 확인할 수 있다.
public class StringTest {
/**
* new 연산자를 이용해 String 객체를 생성하면 Heap 영역에 저장된다.
* 따라서 값이 같더라도 메모리 주소는 동일하지 않다.
*/
@DisplayName("new 연산자로 생성한 String 객체는 메모리 주소가 다르다.")
@Test
void StringNew() {
//heap 영역에 abced를 값으로 가지는 String 객체를 할당한다.
String str1 = new String("abced");
//heap 영역에 abced를 값으로 가지는 String 객체를 할당한다.
String str2 = new String("abced");
Assertions.assertNotSame(str1, str2);
}
}
Immutable하다는 게 무슨말인지부터 알아보자
//[1]
String temp = "abc";
//[2]
temp = "123"
여기서 Immutable 하다는 것을 temp참조 변수에 대해 말하는 것이 아닌
한 번 생성된 후에 값이 변경되지 않는 String Object에 대해 말하는 것이다.
Heap메모리의 String Constant Pool에 1값을 가진 String Object를 생성한 뒤 각 참조변수는 해당 heap 메모리 주소를 가집니다.
String one = "1";
String temp = "1";
String first = "1";
따라서 String Constant Pool에서 같은 값을 가진 String Object를 찾으면 새로운 String Object를 생성할 필요없이 기존의 String Object를 참조하면 됩니다.
String Object는 Immutable하기 때문에 어느 Thread에서도 값을 변경할 수 없다.
따라서 Thread Safe가 보장된다.
public class StringTest {
public static void main(String[] args) {
String one="1";
String temp="2";
String first="1";
printRef(one);
printRef(temp);
printRef(first);
}
private static void printRef(String str){
System.out.println("value = "+str+", ref : "+System.identityHashCode(str));
}
}
value = 1, ref : 1243091471
value = 2, ref : 203086640
value = 1, ref : 1243091471
intern()은 동등한(equals로 값 비교)String Object가 String Constant Pool에 존재하는지 확인하는 메서드이다.
public class StringTest {
@DisplayName("동등한 String 객체가 이미 String Pool에 존재하면 그 객체를 그대로 리턴한다.")
@Test
void internEqualTest() {
String str1 = "abc";
String str2 = new String("abc").intern();
Assertions.assertSame(str1, str2);
}
@DisplayName("동등한 String 객체가 String Pool에 존재 안 하면 String 객체를 String Pool에 추가하고 해당 참조값 반환")
@Test
void internNotEqualTest() {
String str1 = "abc";
String str2 = new String("def").intern();
Assertions.assertNotSame(str1, str2);
}
}
public class StringTest {
public static void main(String[] args) {
String str1 = "abc";
String str2 = new String("abc");
String str3 = str2.intern();
System.out.println(System.identityHashCode(str1));
System.out.println(System.identityHashCode(str2));
System.out.println(System.identityHashCode(str3));
}
}
<확인 결과>
intern 메서드를 통해 str2의 값과 동일한 String Object가 String Constant Pool에 존재하기 때문에 str3은 String Constant Pool과 동일한 참조값을 가지고
str2 기존에 존재하던 String Object의 참조값은 삭제되지 않고 그대로 유지됨을 알 수 있다.
2060468723
622488023
2060468723
Java 디자이너는 모든 종류의 Java 어플리케이션에서 가장 많이 사용되는 데이터 타입이 String이 될 것이라고 예측했었고, 그리고 그것이 처음부터 최적화가 필요한 이유라는 것을 알았다.
이로인해 String Constant Pool을 도입하게 되었고
이를 위해 String은 final 또는 Immutable하게 하였다.