자바를 처음 시작하면 문자열을 다루는 클래스 중 하나인 String을 가장 먼저 배울 것이다.
그리고 좀 더 공부하다보면 StringBuilder, StringBuffer를 배우게 될텐데 왜 이렇게 여러 클래스가 있는 것이고 언제 각 클래스를 써야하는지 정리해보려고 한다.
String
String 클래스는 문자열을 다루는 가장 기본적인 클래스로서 불변성이라는 특성을 가진다.
아래의 사진을 보면 String 클래스가 final로 선언되어 있는 것을 알 수 있다.
String은 불변성을 가지고 있는데, +나 concat을 이용해 값을 추가하면 어떻게 될까? 아래의 예제를 통해 살펴보려고 한다.
public class StringTest {
public static void main(String[] args) {
String str = "Hello";
str += "World!";
System.out.println("value = " + str);
}
}
//실행결과
//HelloWorld!
보통 우리가 문자열을 추가할때 + 연산자를 통해 추가하는데, 이렇게 하면 str이라는 공간에 값이 추가가 된다고 하고 있을 것이다.
나도 그렇게 생각했다..
하지만 +연산자 또는 concat 메서드를 사용하여 문자열을 추가하면 기존 공간의 값이 변경되는 것이 아니고 새로운 공간이 할당된다.
String 클래스의 리터럴은 Heap 메모리 안에 String pool이라는 영역에 할당된다. 위의 예제를 표현하면 아래의 그림과 같다.
HelloWorld! 값을 가지고 있는 새로운 공간이 생기고, 기존에 Hello 값을 가지고 있는 공간은 Garbage가 되어 JVM의 GC에 의해 정리가 된다.
위의 그림은 눈으로 확인을 해볼 수 없으니, 아래의 예제를 통해 실제로 HashCode가 어떻게 출력되는지 확인해보자.
public class StringTest {
public static void main(String[] args) {
String str = "Hello";
System.out.println("Hello Hashcode = " + System.identityHashCode(str));
str += "World!";
System.out.println("value = " + str);
System.out.println("HelloWorld! Hashcode = " + System.identityHashCode(str));
}
}
//실행결과
//Hello Hashcode = 796684896
//value = HelloWorld!
//Hello World! Hashcode = 234698513
분명 같은 str의 Hashcode를 출력했는데 값이 다른 것을 알 수가 있다.
위에서 설명했듯이 String은 불변성이라는 특성을 가지기 때문에 변하지 않는 문자열을 읽어드리는 경우에 좋은 성능을 기대할 수 있다.
하지만 문자열 수정이 빈번하게 일어나는 상황에 String 클래스를 사용하게 되면 Heap 메모리에 많은 Garbage가 생성되어 성능에 안좋은 영향을 끼칠 수가 있다.
그래서 이 문제를 해결하기 위해 StringBuilder와 StringBuffer 클래스가 존재한다.
String 클래스의 + 연산이 컴파일러에서 StringBuilder를 사용하도록 자동변환을 하여 최적화를 해준다고 하는데 이에 관해서는 아래 글을 참고하면 좋을 것 같다.
StringBuilder vs StringBuffer
두 클래스 모두 String 클래스와 반대되는 가변성이라는 특성을 가지고 있다. append() 등의 메서드를 이용하여 한 공간의 값을 수정할 수 있다. 따라서 문자열 수정이 빈번하게 일어나는 상황에서는 String이 아닌 두 클래스를 사용하여 성능을 개선할 수 있다.
아래의 사진들을 보면 두 클래스 모두 AbstractStringBuilder라는 부모 클래스를 상속받는 것을 알 수 있다.
그렇다면 한 클래스만 제공하면 되지 왜 같은 가변성을 가지는데 굳이 두 클래스로 나눠서 제공을 할까?
가장 큰 차이점은 동기화의 유무이다. 위의 사진들을 보면 StringBuffer 클래스의 append() 메서드는 synchronized 키워드가 있어 멀티 쓰레드 환경에서 동기화를 하고 있다. 하지만 StringBuilder 클래스의 append() 메서드는 해당 키워드가 없다.
따라서 StringBuffer는 동기화를 지원하여 멀티쓰레드 환경에서 안전하다.
StringBuilder는 동기화를 지원하지 않아 멀티쓰레드 환경에서 사용하는 것은 적합하지 않다. 하지만 단일 쓰레드 환경에서는 StringBuffer보다 성능이 뛰어나다.
결론
- String 클래스는 문자열 수정이 적은 경우에 사용
- StringBuilder 클래스는 문자열 연산이 많고, 단일쓰레드 또는 동기화를 고려하지 않아도 되는 경우에 사용
- StringBuffer 클래스는 문자열 연산이 많고, 멀티쓰레드 환경인 경우에 사용
'☕️ Java' 카테고리의 다른 글
[Java] List 인터페이스 - ArrayList 클래스 (1) | 2023.05.30 |
---|---|
[Java] Collection Framework (2) | 2023.05.18 |
[Java] Comparable과 Comparator (0) | 2023.04.25 |
[Java] 인터페이스는 왜 다중 상속이 가능할까? (0) | 2023.04.18 |
[Java] toString()을 Override 하는 이유 (0) | 2023.04.07 |