String 객체는 불변 객체(값)이다.
String 에 값을 할당하는 방법은 2가지가 있다.
String str = "abc";
String 을 리터럴 값으로 할당하게 되면, Heap 메모리 영역 안의 특별한 메모리 공간인 String constant pool 에 저장된다.
만약 String constant pool 에 존재하는 리터럴 값을 사용하면, 새로운 리터럴 값을 만드는게 아닌 현재 존재하는 값을 사용하게 된다. 따라서, 아래와 같은 결과를 볼 수 있다.
String str1 = "abc";
String str2 = "abc";
System.out.println(str1 == str2) // true
String str = new String("abc");
new 키워드를 통해 값을 할당하면, Heap 메모리 영역 안에 동적으로 메모리 공간이 할당 된다.
같은 문자열이더라도 new 키워드를 통해 String 객체를 생성하면, Heap 영역 안의 다른 메모리 공간을 참조하게 된다. 따라서, 아래와 같은 결과를 볼 수 있다.
String str1 = new String("abc");
String str2 = new String("abc");
System.out.println(str1 == str2) // false
String 은 불변 객체이므로, 아래와 같이 변경이 일어날 경우 객체 안의 값이 변경되는 것이 아니라 새로운 객체가 생성된다.
연산이 수행될 때 마다 두 문자열을 모두 읽어 들이고, 새로운 메모리에 복사를 하기 때문에 성능이 좋지 않다.
String str1 = "abc"; // value: abc, address: 1111
String str2 = "def"; // value: def, address: 2222
str1 = str1 + str2 // value: abcdef, address: 3333
이전에 참조하던 address: 1111 객체는 쓰레기가 되고 나중에 GC 에 의해 처리된다.
StringBuffer 와 StringBuilder 는 AbstractStringBuilder 라는 추상 클래스를 상속받아 구현되어 있는 가변 클래스이다.
AbstractStringBuilder 추상클래스에는 2가지 멤버 변수가 존재한다.
예시를 통해 문자열 연산 과정을 간략히 보면 다음과 같다.
StringBuilder sb = new StringBuilder();
StringBuilder 를 생성할 때 capacity 를 지정하지 않으면 기본 16으로 설정된다.
sb.append("first string");
sb.append("second string");
String 과 달리, 매번 문자열을 복사하고 메모리를 할당하는 과정이 없어 성능상 이점이 존재한다.
StringBuffer는 synchronized 키워드를 사용하여 동기화를 지원한다. 즉, 멀티 스레드 환경에서도 안전하게 동작할 수 있다.
StringBuilder 는 동기화를 지원하지 않는다. thread safe 하지 않지만, StringBuffer 보다 성능상의 이점을 갖는다.