Java의 String, StringBuffer, StringBuilder 클래스
String 클래스는 문자열 관련 기능을 제공하는 클래스이다. 문자열을 저장하기 위해 문자형 배열 참조변수(char[]
) value를 인스턴스 변수로 정의하고 있다. 인스턴스 생성 시 생성자의 매개변수로 받는 문자열은 이 인스턴스 변수 value에 문자형 배열(char[]
)로 저장된다.
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** The value is used for character storage. */
private final char value[];
...
}
String str1 = "abc"; // 문자열 리터럴
String str2 = new String("abc");
문자열을 만들 때는 문자열 리터럴을 지정하는 방법과 String 클래스의 생성자를 통해서 만드는 방법이 있다. 문자열 리터럴은 똑같은 문자열이 있다면 그것을 재사용하지만 String 클래스의 생성자를 사용하는 경우 new
연산자에 의해 메모리 할당이 이루어지기 때문에 항상 새로운 String 인스턴스가 생성된다.
문자열 리터럴은 상수 저장소(constant pool)란 곳에 저장되는데 같은 내용의 문자열 리터럴이 이 constant pool에 있는지 찾은 후 있다면 값을 참조하고 없다면 새로 등록한다. 이처럼 문자열 리터럴은 똑같은 내용일 경우 같은 객체를 가리키므로 동등비교 연산자(==
)로 비교하면 true
를 얻는다. 반면 new
연산자로 생성한 문자열은 내용은 같지만 서로 다른 객체를 가리키므로 동등비교 연산자를 사용할 경우 false
를 반환한다. 내용이 같은 문자열인지 비교하려면 equals 메서드를 사용해야 한다. equals 메서드는 String 클래스에서 내용 비교를 위해 오버라이딩 되어 있다.
String str1 = "abc";
String str2 = "abc";
String str3 = new String("abc");
String str4 = new String(str1);
System.out.println(str1 == str2); // true
System.out.println(str1 == str3); // false
System.out.println(str1 == str4); // false
System.out.println(str3 == str4); // false
System.out.println(str1.equals(str3)); // true
System.out.println(str1.equals(str4)); // true
System.out.println(str3.equals(str4)); // true
한번 생성된 String 인스턴스는 값을 읽어 올 수만 있고 변경할 수는 없다. 결합 연산자(+
)를 이용해 문자열을 합치는 경우 인스턴스 내의 문자열이 바뀌는 것이 아니라 새로운 문자열이 담긴 String 인스턴스가 생성된다. 이처럼 한번 생성된 후 상태를 변경할 수 없는 객체를 불변(immutable) 객체라고 한다. (반대되는 개념으로 가변(mutable) 객체가 있다.) 결합 연산자를 사용할 때마다 새로운 문자열을 만들어 메모리 공간을 차지하게 되므로 가능한 이런 연산을 줄이고 문자열을 다루는 작업이 많을 경우 String 클래스 대신 StringBuffer 클래스를 사용하는 것이 좋다. StringBuffer 인스턴스에 저장된 문자열은 변경이 가능하므로 하나의 인스턴스만으로도 문자열을 다루는 것이 가능하다.
불변 클래스인 String을 대신해 내부적으로 문자열 편집을 위한 버퍼(buffer)를 가지고 있는 StringBuffer 클래스를 이용하면 쉽게 문자열을 변경할 수 있다. StringBuffer 인스턴스를 생성할 때 버퍼 크기를 지정할 수 있는데, 편집할 문자열의 길이를 고려하여 버퍼의 길이를 충분히 잡아주는 것이 좋다. 편집 중인 문자열이 버퍼의 길이를 넘어서면 버퍼의 길이를 늘려주는 작업이 추가로 수행되어 작업 효율이 떨어지기 때문이다.
StringBuffer 클래스도 String 클래스처럼 내부에 문자형 배열을 갖는다. 이 배열은 인스턴스를 생성할 때 문자열을 저장하고 편집하기 위한 공간(buffer)로 사용된다. StringBuffer 인스턴스에 저장된 문자열의 길이를 지정해 주지 않으면 기본적으로 16개의 문자를 저장할 수 있는 크기의 버퍼를 생성한다.
String과 달리 StringBuffer 인스턴스는 가변 객체이므로 내용을 변경할 수 있다. 다음과 같이 코드를 작성했다고 가정하자.
StringBuffer sb = new StringBuffer("abc");
sb.append("123");
StringBuffer sb2 = sb.append("ZZ");
System.out.println(sb); // abc123ZZ
System.out.println(sb2); // abc123ZZ
append 메서드의 반환타입은 StringBuffer인데 자기 자신을 반환한다. 그러므로 새로운 인스턴스를 생성하지 않고 계속해서 자신을 가지고 연산하는 것이다.
String 클래스에서는 equals 메서드를 문자열의 내용을 비교하도록 오버라이딩하였지만 StringBuffer 클래스에서는 오버라이딩되지 않았기 때문에 StringBuffer 클래스의 equals 메서드를 사용할 경우 동등비교 연산자로 비교한 것과 같은 결과를 얻는다.
StringBuffer sb1 = new StringBuffer("abc");
StringBuffer sb2 = new StringBuffer("abc");
System.out.println(sb1 == sb2); // false
System.out.println(sb1.equals(sb2)); // false
반면 toString 메서드는 오버라이딩되어 있어 StringBuffer 클래스에서 이를 호출하면 담고 있는 문자열을 String으로 변환하여 반환한다. 따라서 StringBuffer 인스턴스에 담긴 문자열을 비교하기 위해서는 StringBuffer 인스턴스의 toString 메서드로 String 인스턴스를 얻어 String 클래스의 equals 메서드를 호출해야 한다.
System.out.println(sb1.toString().equals(sb2.toString())); // true
StringBuffer 클래스는 멀티 스레드 환경에서 안전하도록(tread-safe) 연산을 수행할 때 동기화되도록 설계되었다. 동기화는 성능을 떨어뜨리는 요인이 되므로 멀티 스레드 환경이 아닌 경우에는 불필요하다. 그래서 싱글 스레드 환경에 적합한, StringBuffer의 스레드 동기화 기능을 뺀 StringBuilder 클래스가 추가되었다. StringBuilder는 단지 동기화만 보장하지 않을 뿐 StringBuffer와 같은 기능을 가지고 있다.