💻 Dev/Java & OOP

Immutable(불변성), StringBuffer와 StringBuilder

현주먹 2024. 10. 29. 23:21
String str = "Hello";
System.out.println("String 객체의 주소 : "+str.hashCode());
str = str + " World";
System.out.println("String 객체의 주소 : "+str.hashCode());

 

리터럴로 선언한 문자열 객체의 주소를 출력하고, 문자열만 더한 다음 객체 주소를 다시 출력해 보는 코드이다.

 

다시 변수를 만든 것도 아닌데 주소가 같지 않을까?

String 객체의 주소 : 3541040
String 객체의 주소 : 1758230625

하지만 아니다.

 

왜일까?
자바에서 String 객체는 기존 객체를 재사용하지 않는다, 같은 객체에 더한다고 해도 새로운 객체가 생성되고 기존 객체는 GC의 대상이 된다.
즉, String 클래스는 Immutable, 불변성을 갖는다.

 

그래서 변경이 자주 일어나는 프로그램에서 String만 사용하게 되면 효율적인 성능을 기대하기 어렵다.

 

이 단점을 보완하기 위해 나온 클래스가 StringBufferStringBuilder다.

 

StringBuffer와 StringBuilder

public final class StringBuffer  
   extends AbstractStringBuilder  
   implements Serializable, Comparable<StringBuffer>, CharSequence

public final class StringBuilder  
    extends AbstractStringBuilder  
    implements java.io.Serializable, Comparable<StringBuilder>, CharSequence

 

StringBufferStringBuilder 두 클래스 모두 AbstractStringBuilder 라는 추상 클래스를 상속받고 있다.

 

 

AbstractStringBuilder 추상 클래스의 멤버 변수엔 2가지 변수가 존재한다.

byte[] value; //The value is used for character storage.
int count; //The count is the number of characters used.

 

두 클래스 모두 문자열을 수정할 때 append() 메서드를 사용한다.

public AbstractStringBuilder append(String str) {  
    if (str == null) {  
        return appendNull();  
    }  
    int len = str.length();  
    ensureCapacityInternal(count + len);  
    putStringAt(count, str);  
    count += len;  
    return this;}

문자열을 추가하게 되면 추가할 문자열의 크기(길이)만큼 현재의 문자열을 저장하는 배열의 크기를 늘려주고,

그 공간에 문자열을 넣어주는 방식이다.

 

즉, 같은 주소공간을 참조하며 값이 변경되기 때문에 두 클래스는 Muttable, 가변성을 가진다.

 

이 두 클래스에서도 차이점이 존재하는데 바로 동기화(Synchronization)이다.

@Override  
@HotSpotIntrinsicCandidate  
public synchronized StringBuffer append(String str) {  
    toStringCache = null;  
    super.append(str);  
    return this;}

StringBuffer는 메서드에서 synchronized 키워드를 사용하기 때문이다.


synchronized 키워드는 자바에서 동기화를 구현하는 키워드로, synchronized 블록을 사용하고 있는 스레드 외에 다른 스레드가 접근할 수 없도록 차단하는 것이다.

 

즉, 여러 스레드의 동시 접근을 막아 멀티스레드 환경에서 데이터의 일관성을 보장할 수 있다.

 

StringBuilder는 동기화가 없어 멀티스레드 환경에서 안전하지 않지만, 그로 인한 성능 저하가 없어 단일 스레드에서 더 빠르다.

 

 

문자열의 추가, 수정, 삭제 등 변경이 빈번한 경우 각각 언제 사용하는 게 좋을까?

  • StringBuffer - 멀티스레드 환경
  • StringBuilder - 단일스레드 환경

 

 

참고

StringBuilder와 StringBuffer는 무슨 차이가 있는가?