Develop/Java

[Java] 스트링풀(String Pool)

순무엄마동생 2022. 12. 31. 22:24

회사 동기가 문자열 비교 중 어떤건 equals를 쓰고 어떤건 ==를 쓰는데 똑같이 동작한다며 뭐가 다른건지 물어봤다.

내가 알기론 문자열에서 == 는 주소값을 비교하며 equals를 사용해야 문자가 실제로 일치하는지 여부를 판단하는 걸로 알고 있다. ==를 사용했음에도 동일하게 동작한 이유는 자바 string의 특징이 immutable하고 객체 선언을 하지 않는다면 String Pool에서 동일한 값을 가져와 사용하는 것으로 알고 있는데 이게 맞는지 확인 겸 다시 찾아봤다.

 

코드 결과값 비교

문자열 선언 방법에는 2가지가 있다.

  1. 문자열을 그대로 선언하는 리터럴 선언
  2. new String()을 사용한 객체 선언

이 두가지의 내용을 코드로 작성하여 ==과 equals가 나타내는 결과값을 확인하면 아래와 같다.

public class Main {
    public static void main(String[] args) {
        String s1 = "string";
        String s2 = "string";
        String s3 = new String("string");

        System.out.println(s1 == s2);       // true
        System.out.println(s1.equals(s2));  // true

        System.out.println(s1 == s3);       // false
        System.out.println(s1.equals(s3));  // true
    }
}

리터럴 선언을 한 s1과 s2는 ==와 equals의 결과값이 모두 참인 것을 볼 수 있다.

반면, 리터럴 선언한 s1과 객체 생성한 s2를 비교한 경우, equals 결과값만 참임을 볼 수 있다.

 

Equals

우선, equals라는 메소드가 어떻게 이루어 졌는지 코드를 살펴보면 아래와 같다.

public boolean equals(Object anObject) {
        if (this == anObject) {
            return true;
        }
        if (anObject instanceof String) {
            String anotherString = (String)anObject;
            int n = value.length;
            if (n == anotherString.value.length) {
                char v1[] = value;
                char v2[] = anotherString.value;
                int i = 0;
                while (n-- != 0) {
                    if (v1[i] != v2[i])
                        return false;
                    i++;
                }
                return true;
            }
        }
        return false;
    }

객체의 문자열을 하나씩 비교하여 값이 하나라도 다르면 false를 return 하도록 되어 있는 것을 알 수 있다. s1, s2, s3의 문자열이 string으로 모두 일치하므로 true의 결과값을 낸 것이다.

 

항등 연산자(==)

==의 값은 왜 다를까?

그 이유는 ==는 메모리 값을 참조하기 때문이다.

 

여기서 알 수 있는 것은 리터럴 선언의 경우, 동일한 내용이라면 동일한 메모리 주소를 가리킴을 의미한다. Java에서 객체를 생성하는 경우, Heap 영역에 생성되니 모든 객체별로 각자의 메모리 주소를 가지고 있다. 

 

String Constants Pool & String Interning

그렇다면 리터럴 선언을 한 경우 어디에 값이 저장되며 왜 같은 메모리 주소를 가지는가?

답은 String Constants Pool에 저장되며 이 곳에 저장된 String 값은 불변성을 갖게 된다. 불변성(Immutability)는 값이 변함이 없으며 동일한 값을 가지고 있다면 같은 곳을 가리킨다는 의미이다.  이런 프로세스를 String Interning이라고 하는데 실제 객체 선언을 한 문자열에 intern 메소드를 사용하면 풀에 값을 저장한다.

 

코드로 살펴보자

public class Main {
    public static void main(String[] args) {
        String s1 = "string";
        String s2 = "string";
        String s3 = new String("string").intern();

        System.out.println(s1 == s2);       // true
        System.out.println(s1.equals(s2));  // true

        System.out.println(s1 == s3);       // true
        System.out.println(s1.equals(s3));  // true
    }
}

아까와 다르게 s3에 intern 메소드를 적용했다. 그 결과, 리터럴 선언을 한 것과 동일한 메모리와 값을 갖는 것을 확인할 수 있었다. 

 

추가적으로 Java 버전 6이하는 Perm이라는 공간 스트링 풀이 있는 반면 버전 7부터는 Heap 영역내에 존재하여 GC 대상이 된다고 하는데 이건 JVM 동작원리와 엮어서 좀 더 찾아봐야겠다. 

 

'Develop > Java' 카테고리의 다른 글

[Java] JVM 구조 및 동작원리 - 1  (0) 2023.01.03
[Java/디자인패턴]Singleton/싱글톤  (0) 2021.01.22