🍇

자바에서는 생성자를 이용해 객체를 생성한다. 그런데 왜 문자열은 생성자를 이용하지 않을까, 다른 정수 타입처럼 왜 문자열을 바로 선언하는 것일까?
자바에서 생성자에는 아래와 같은 주석이 있다. 명시적으로 똑같은 문자열을 복사할 경우에만 생성자를 사용하고 이외의 경우에는 필요없다고 한다.

Initializes a newly created String object so that it represents the same sequence of characters as the argument; in other words, the newly created string is a copy of the argument string. Unless an explicit copy of original is needed, use of this constructor is unnecessary since Strings are immutable.

그리고 그 이유는 자바에서의 문자열은 불변하기 때문이라고 한다.
불변하다는 것은 객체가 변하지 않는다는 건데, 실제로 그럴까? 그렇다면 자바에서의 문자열은 값이 같다면 서로 같은 객체일까?

생성자를 이용한 문자열 객체 생성

먼저 생성자를 통해 문자열 객체를 두개를 생성해보면 두 객체는 같은 문자열을 가지지만 실제 객체는 서로 다른 객체이다.

123456789101112
public class Main {
public static void main(String args[]) {
String foo = new String("foo");
String anotherFoo = new String("foo");
System.out.println("foo == anotherFoo: " + (foo == anotherFoo)); /// false
System.out.println("foo.equals(anotherFoo): " + foo.equals(anotherFoo)); /// true
System.out.println("System.identityHashCode(foo): " + System.identityHashCode(foo));
System.out.println("System.identityHashCode(anotherFoo): " + System.identityHashCode(anotherFoo));
}
}

두 객체의 동등성 비교를 위해 ==연산자와 equals메소드를 사용해보면 ==의 결과는 'false', eqauls메소드의 결과는 'true'이다.
이 뜻은 두 문자열은 서로 다른 메모리에 할당된 다른 객체이지만, 문자열의 값은 같다는 뜻이다. 실제로 String.classequals 메소드를 확인해보면 문자열이 가진 값을 비교하고 있다.
명시적으로 생성자를 통해 문자열 객체를 생성하면 서로 다른 객체다.

쌍따옴표를 이용한 문자열 객체 생성

그렇다면 명시적으로 생성자를 통해 문자열을 생성하지 않는다면 어떨까?

123456789101112
public class Main {
public static void main(String args[]) {
String zoo = "zoo";
String anotherZoo = "zoo";
System.out.println("zoo == anotherZoo: " + (zoo == anotherZoo));
System.out.println("zoo.equals(anotherZoo): " + zoo.equals(anotherZoo));
System.out.println("System.identityHashCode(zoo): " + System.identityHashCode(zoo));
System.out.println("System.identityHashCode(anotherZoo): " + System.identityHashCode(anotherZoo));
}
}

이전에 명시적으로 생성한 것과 달리 이제는 두 객체가 서로 같은 객체이다. 사실 두 객체라고 볼 수 없다. 하나의 객체를 zooanotherZoo로 가리키고 있는 상황이기 때문이다.
그러면 생성자를 이용해서 생성하는 것과 쌍따옴표("")로 문자열을 선언하는 것은 어떠한 차이가 있는 것일까?

자바에서의 문자열 객체는 다른 객체와 다르게 문자열 pool을 이용해서 관리하고 있다. 문자열을 쌍따옴표로 선언하면 내부에 있는 문자열 pool에 해당 문자열이 있는지 확인을 먼저 한다.
만약 있다면 해당 문자열을 반환하고 없다면 새롭게 문자열 객체를 생성하고 문자열 pool에 등록한 후에 반환한다. String.classintern메소드를 살펴보자.
한번 선언한 문자열은 문자열 pool에 등록하고 이를 계속 사용하는 것이다. 위의 예처럼 "zoo"를 아무리 계속 선언해도 문자열 pool에서 같은 문자열 객체를 가져오는 것이다.

12345678910111213
public class Main {
public static void main(String args[]) {
String upperZoo = zoo.toUpperCase();
// upperZoo.intern();
String bigZoo = "ZOO";
System.out.println("bigZoo == upperZoo: " + (bigZoo == upperZoo));
System.out.println("bigZoo.equals(upperZoo): " + bigZoo.equals(upperZoo));
System.out.println("System.identityHashCode(bigZoo): " + System.identityHashCode(bigZoo));
System.out.println("System.identityHashCode(upperZoo): " + System.identityHashCode(upperZoo));
}
}

문자열 pool을 사용하는지 직접적으로 확인을 해보면 바로 알 수 있다. 위의 "ZOO" 대문자로 선언된 문자열 객체와 zootoUpperCase 메소드를 이용해 생성된 upperZoo 객체가 있다.
intern메소드를 호출하지 않고 upperZoobigZoo의 동등성을 비교하면 서로 다른 객체임을 확인할 수 있다.
그런데 intern메소드를 bigZoo문자열을 선언하기 전에 먼저 호출을 한다면 upperZoobigZoo의 동등성을 비교하면 서로 같은 객체임을 알 수 있다.

자바에서의 문자열은 여느 다른 객체처럼 동작하는 것처럼 보이지만 조금은 다른 부분이 있다.
이렇게 문자열 pool을 내부적으로 관리하는 이유는 아무래도 객체 생성과 파괴를 조금이나마 줄이는 노력이지 않을까? 이 부분은 더 찾아 봐야겠다.