String 변수를 선언하는 2가지 방식

String 변수를 비교할 땐, 비교연산자 == 대신 String 클래스의 equals()를 사용해야한다. 왜 그럴까? 아래 코드를 통해 확인해보자.

1
2
3
4
String greeting = "Hello World.";
String hi = new String("Hello World.");

System.out.println(greeting==hi);

위에서 greetinghi는 서로 같은 문자열 리터럴을 갖고있으므로 비교연산자로 비교하면 같을것 같지만, == 연산자는 객체를 비교하는 연산자 인데 두 변수는 두 서로 다른 객체이기 때문에, false를 반환한다.

따라서 문자열을 비교할땐, == 대신 String 클래스의 equals()를 이용해서 비교해야 한다. equals()는 객체가 다르더라도 문자열 값만 비교하므로 원하는 결과를 반환받을 수 있다.

1
2
3
4
5
6
7
8
9
10
String greeting = "Hello World.";
String hello = "Hello World.";
String hi = new String("Hello World.");

System.out.println("greeting==hi : "+greeting==hi);
System.out.println("greeting.equals(hi) : "+greeting.equals(hi)+"\n");

System.out.println("greeting 주소값 : "+System.identityHashCode(greeting));
System.out.println("hello 주소값 : "+System.identityHashCode(hello));
System.out.println("hi 주소값 : "+System.identityHashCode(hi));
1
2
3
4
5
6
greeting==hi : false
greeting.equals(hi) : true

greeting 주소값 : 640070680
hello 주소값 : 640070680
hi 주소값 : 1510467688

System 클래스의 identityHashCode()를 이용해서 주소값을 확인해보니 greetinghi는 다른 객체로 보여진다. 그래서 비교연산자를 통한 비교에서 false를 반환받은 것이다.

구글링해보니 일반적으로 객체는 new 키워드를 이용하여 생성자로 생성해야하나 String 변수에 한해 리터럴 대입을 허용한다고 한다. 그렇다면 문자열 리터럴을 대입하는것과 new 키워드로 String 객체를 생성하는 것은 어떤 차이가 있을까.

문자열 리터럴을 대입하여 변수를 생성하면, 해당 변수는 JVM의 String Constant Pool 에 할당된다고 한다. 이름에서도 알 수 있듯이 상수로 사용되기 때문에 여기에 할당되면 가변성(immutable)이 허용되지 않는다.

반면 생성자를 통해 문자열 변수를 생성하면, Heap 메모리에 할당되어 값의 변경이 가능해진다고 한다. 정말 그런지 주소값을 통해 확인해보겠다.

1
2
3
4
5
6
7
8
9
10
11
String constantStr01 = "Hello";
String constantStr02 = "Hello";

System.out.println("constantStr01 주소값 : "+System.identityHashCode(constantStr01));
System.out.println("constantStr02 주소값 : "+System.identityHashCode(constantStr02));

String heapStr01 = new String("hello");
String heapStr02 = new String("hello");

System.out.println("heapStr01 주소값 : "+System.identityHashCode(heapStr01));
System.out.println("heapStr02 주소값 : "+System.identityHashCode(heapStr02));
1
2
3
4
constantStr01 주소값 : 225534817
constantStr02 주소값 : 225534817
heapStr01 주소값 : 640070680
heapStr02 주소값 : 1510467688

같은 값을 갖는 두개의 문자열 변수를 각각 리터럴 대입방식(String Constant Pool에 할당)과 생성자 생성방식(Heap에 할당)으로 선언하여 주소값을 비교한 결과이다.

리터럴 대입 방식을 통해서 선언한 문자열 변수들은 서로 같은 주소값을 갖고 있었다. String Constant Pool을 자세히 알지는 못하지만, 참조하는 값이 이미 할당된 변수가 있을 경우, 해당 변수가 바라보는 주소값을 참조하는것 같다.

반면 생성자를 통해 선언한 문자열 변수는 같은 값을 갖더라도 서로 다른 주소값을 갖는걸 알 수 있다.

결과적으로 문자열 변수 4개를 선언했지만, 주소값을 통해 확인된 객체는 3개이므로 메모리를 효율적으로 관리하기 위해서는 생성자를 통해서 문자열 변수를 선언하는것보단 리터럴을 대입하는 방식으로 선언하는게 더 나을것 같다.

출처