String 리터럴? String Constant Pool?

Java에서 가장 많이 사용하는 객체자료형인 String,

기본자료형같은 이 String은 레퍼런스자료형으로 많은 내용들을 공부를 해야 합니다.

그 중에 String 객체를 생성하는 방식에 따른 차이점에 대해서 다룰려고 합니다.

String은 객체를 생성하는 2 가지 방식을 사용합니다.

1) String 리터럴, 큰 따옹표("")를 사용하는 방식
2) new 연산자를 사용하는 방식

테스트를 진행해봅시다.

public class StringTest {
    public static void main(String[] args) {

        String s1 = "Cat";
        String s2 = "Cat";
        String s3 = new String("Cat");

        if (s1 == s2) {
            System.out.println("str1 == str2 result: true");
        } else if (s1 == s3) {
            System.out.println("str1 == str3 result: true");
        } else  {
            System.out.println("strTest result: false");
        }
    }
}

결과

str1 == str2 result: true

(==) 연산자로 비교하면 리터럴방식과 new 연산자방식에서 차이가 나는 것을 확인할 수 있습니다.

String s1 = "Cat";String s2 = "Cat";는 값과 주소값이 일치합니다. 그렇기 때문에 (==) 연산자로 비교하면 같게 나오는 것이 true인 것입니다.

그에 반해, String s1 = "Cat";String s3 = new String("Cat");는 값은 같더라도 주소값이 다르기에 false가 나오는 것입니다.

여기에는 JVM내의 Java String Pool과 관련이 깊습니다.


Java String Pool

흔히 String으로 어떤 값을 할당할 때 new 연산자로 하는 것은 보지 못했을 겁니다. new 연산자로 String 객체를 생성하지 않는 것이 좋지 않기 때문입니다. 위에 테스트 code만 봐도 값이 같지 않게 판단하니깐요.

이렇게 된 근본적인 이유는 JVM내 메모리 할당의 차이가 있기 때문입니다.

그림을 살펴봅시다.


이미지 출처 : https://starkying.tistory.com/entry/what-is-java-string-pool

Heap 영역에는 "Cat", "Dog"과 같은 '값'들이 들어가게 되는데, 그림의 우측을 보면 중요한 차이를 발견할 수 있습니다.

  • String literal로 생성한 객체는 "String Pool"에 들어간다.
  • String literal로 생성한 객체의 값(ex. "Cat")이 이미 String Pool에 존재한다면, 해당 객체는 String Pool의 reference를 참조한다. 그림에서 s1과 s2가 같은 곳을 가리키고 있는 것도 이 때문이다.
    new 연산자로 생성한 String 객체는 같은 값이 String Pool에 이미 존재하더라도, Heap 영역 내 별도의 객체를 가리키게 된다.

String Pool 은 Constant Pool과 같은 말이다.

Constant(String) Pool의 존재 이유는 String의 같은 값이 할당될 때 재사용하기 위함이다.



정리

String 객체를 new 연산자로 생성하면, 같은 값이라 할지라도 Heap 영역에 매번 새로운 객체가 생성되어 Constant Pool에 들어가지 않는다. 그래서 비교시 값이 같더라도 (==)연산자를 쓸 때는 주소값이 달라 다르다고 판단한다. 따라서 String이 갖는 불변성이라는 장점을 누리지 못한다.

결국 메모리를 효율적으로 사용하기 위해서는 항상 String literal(큰 따옴표)로 String을 생성하는 것이 좋겠다.

주의 equals() 메서드 비교는 어떨까?
equals() 메서드는 객체의 값만 확인하기 때문에 리터럴 방식과 new 연산자 방식의 결과가 달라진다!

예제)

String a = "aaa";

String b = a;

String c = new String ("aaa");

a, b, c 모두 "aaa" 라는 내용을 가지고 있지만 주소값에 대해서는 다른 값을 가지는 변수가 존재한다. 세 문자열이 주소값을 할당받는 내용을 그림으로 표현해보자.

결과

이미지 출처 : https://ojava.tistory.com/15

회색 테두리가 문자열 변수 a, b, c를 표현한 내용이다.

그림에서는 a, b, c 모두 같은 aaa라는 문자열 내용을 가지고 있지만, a, b는 500이라는 임의의 주소값을 할당 받았으며, c는 600이라는 임의의 주소값을 할당받았다.

내용은 같지만 c가 다른 주소값을 할당받은 이유는 "aaa" 라는 문자열을 대입한 것이 아니라 new String ("aaa") 를 통해 새로운 문자열을 선언하였기 때문이다.

자, 아래 equals, == 을 이용한 아래의 내용이 어떻게 나올지 알아보자.

System.out.println( a.equals(b));

System.out.println( a==b);

System.out.println( a==c);

System.out.println( a.equals(c));

결과

첫번째는 a와 b가 가지고 있는 내용을 비교하였으므로 true
두번째는 a와 b가 가지고 있는 주소값을 비교하고 있으므로 true
세번째는 a와 c가 가지고 있는 주소값을 비교하고 있으므로 false
네번째는 a와 c가 가지고 있는 내용을 비교하였으므로 true


참고

https://starkying.tistory.com/entry/what-is-java-string-pool

https://ojava.tistory.com/15

좋은 웹페이지 즐겨찾기