[Java] String, StringBuilder, StringBuffer의 차이점
자바에 문자열을 담을 수 있는 클래스로 String, StringBuilder, StringBuffer라는 클래스들이 있는데 약간씩 차이가 있다.
일단 가장 큰 차이점은 String은 문자열을 변경할 수 없지만 StringBuilder, StringBuffer는 문자열을 변경 가능한 variable 클래스이다.
String 클래스는 final 클래스이다.
클래스가 final이라는 말은 String 클래스를 상속받아 파생클래스를 만들 수가 없다는 것이다.
또한 String 클래스는 Comparable 인터페이스를 구현(implement)한 클래스이다. Comparable 인터페이스의 구현 클래스는 compareTo라는 메소드를 반드시 구현해야 하므로 String 클래스 안에는 compareTo 메소드가 있다.
두 객체를 비교하기 위해서는 compareTo 메소드가 있어야 한다.
예를 들어
class Person {
}
public class PersonTest {
public static void main(String args[]){
Person p1 = new Person();
Person p2 = new Person();
}
}
이렇게 선언을 하고 p1과 p2를 비교하는 것은 불가능하다. 비교 기준이 없기 때문이다.
만약에 p1 객체와 p2 객체를 비교하고 싶다면
class Person implements Comparable {
int age;
String name;
public Person(int age, String name) {
this.age = age;
this.name = name;
}
@Override
public int compareTo(Person p) {
return age - p.age;
}
@Override
public String toString() {
return name + "(" + age + ")";
}
}
public class PersonTest {
public static void main(String[] args) {
Person p1 = new Person(15, "철수");
Person p2 = new Person(12, "영희");
Person p3 = new Person(17, "순이");
ArrayList people = new ArrayList<>();
people.add(p1);
people.add(p2);
people.add(p3);
Collections.sort(people);
System.out.println(people);
}
}
이렇게 Comparable 인터페이스를 상속받아 compareTo 메소드를 통해 비교기준을 제시해야 한다.
이 코드에서는 나이를 비교 기준으로 제시하였다.
이 코드를 실행한 결과는 다음과 같다.
String 클래스는 compareTo 메소드가 구현되어 있으므로 비교 기준이 설정되어 있다. 그 비교 기준은 알파벳 순서 오름차순이고 소문자보다 대문자가 우선이다. 예를 들어 C, c, d, D가 있고 이것을 정렬한다면 C, D, c, d가 된다. String 클래스는 final 클래스이기 때문에 이러한 비교기준을 변경할 수 없다.
String 객체는 정적인 문자열이다. 즉, 변경 불가능한 고정 문자열이라는 뜻이다.
'String 객체도 + 연산자 써서 문자열 변경 가능하던데?' 할 수 있다. 하지만
String s = "a"; //①
이렇게 해 놓고
s = s + "b"; //②
하면 ①에서의 객체 s와 ②에서의 객체 s는 전혀 다른 객체이다. ①에서의 그 객체에 "b"를 붙여서 "ab"가 되는 것이 아니라 ①에서의 "a" 문자열 객체는 가비지가 되어버리고 "ab"라는 새로운 객체를 만들어 s라는 변수가 새로 만들어진 객체 "ab"를 가리키도록 하는 것이다. 그러나 StringBuffer와 StringBuilder는 그렇지 않다.
StringBuilder와 StringBuffer는
StringBuffer sb = "a"; //또는 StringBuilder sb = "a" //③
이렇게 해 놓고
sb.append("b"); //④
해도 ③에서의 객체와 ④에서의 객체는 같은 객체이고 그 객체에 "b"라는 문자열을 append 한 것 뿐이다. 객체를 새로 만드는 과정이 이루어지지 않는다. (참고로 String은 고정문자열이니 당연히 append 메소드가 없고 StringBuilder와 StringBuffer는 가변 문자열이므로 append 메소드가 있다. 대신 + 연산자로 문자열을 붙이는 것은 불가능하다.)
따라서 문자열이 자주 바뀌어야 한다면 String보다는 StringBuilder나 StringBuffer를 쓰는게 시간적으로나 메모리 공간적으로나 훨씬 효율적이다.
시간적으로 StringBuilder/StringBuffer가 더 효율적이라는 사실은 실제로 이 코드를 돌려보면 알 수 있다.
public class StringTest {
public static void main(String[] args) {
String s = "a";
long before = System.nanoTime();
appendString(s);
long after = System.nanoTime();
System.out.println("String을 썼을 때에 걸린 밀리초: " + (after - before));
StringBuilder sb = new StringBuilder("a");
before = System.nanoTime();
appendStringBuilder(sb);
after = System.nanoTime();
System.out.println("StringBuffer를 썼을 때에 걸린 밀리초: " + (after - before));
}
static void appendString(String s){
for(int i = 0; i < 1000; i++){
s = s + "a";
}
}
static void appendStringBuilder(StringBuilder s){
for(int i = 0; i < 1000; i++){
s.append("a");
}
}
}
위의 코드를 돌린 결과는 아래와 같다.
String을 사용했을 때, StringBuilder를 사용했을 때보다 약 500배 정도 더 걸리는 것을 볼 수 있다.
다만 StringBuilder와 StringBuffer의 차이점은 StringBuffer는 동기화 기능이 있어서 멀티 스레딩을 해야 할 때 사용한다. 따라서 멀티 스레딩을 쓰지 않는다면 StringBuilder를 사용하는 것이 일반적이다.