String 파헤치기

2022년 02월 25일

TOC

본 게시글은 우테코 강의를 참조하여 작성되었습니다.

String class

String 선언의 차이

String string1 = "abc";
String string2 = "abc";
String string3 = new String("abc");

위의 코드는 String class를 만드는 두가지 방법을 나타낸다. 두가지 방법은 보기에는 같은 결과가 나온다고 생각할 수 있지만 내부적으로는 다른 결과를 냅니다. string1과 string2는 스트링 풀(String pool)에 있는 같은 객체를 바라보게 됩니다. 반면에 new String()을 통해 생성한 string3의 경우는 힙 메모리에 새로운 인스턴스를 만들어 괸리를 하게 됩니다. 예시 코드를 작성하여 수행해보면 다음과 같은 결과가 나옵니다.

public class StringTest {

	public static void main(String[] args) {
		String string1 = new String("abc");
		String string2 = new String("abc");

		System.out.println(string1 == string2); // false

		String string3 = "abc";
		String string4 = "abc";

		System.out.println(string3 == string4); // true
	}
}

위의 코드의 경우 new String을 사용하여 새로운 인스턴스를 생성한 string1, string2의 경우는 서로 다른 주소값을 가르켜 false라는 결과를 반환합니다. 반면에 스트링 풀의 주소만을 가르키며 생성한 string3, string4의 경우는 값이 같다는 결과가 나오게 됩니다.

String이 같은지 비교할 때는 동등성 비교!?

string3과 string4는 같은 객체를 바라본다는데 어째서 둘을 비교할 때 동일성(==)이 아닌 동등성(equals)으로 같은지 체크할까요? Java8 이후로는 String string1 = "abc"와 같이 선언한 내용도 GC의 지시 대상이 되어서 다른 객체가 될 수 있습니다. 그래서 String 객체들의 비교는 동일성이 아닌 동등성으로 체크합니다.


StringBuilder, StringBuffer

String은 final로 만들어져 인스턴스의 값은 한번 생성하면 변경이 불가능합니다. 그래서 concat과 문자열 덧셈 과 같이 string의 값을 변경하려고 한다면 기존 인스턴스는 남아있고 남아있는 기존 인스턴스를 사용하여 새로운 인스턴스를 만들게 되어 메모리 낭비가 발생합니다.

Java8이후로는 "ab+"bc"+"cd"와 같은 문자열 더하기 연산을 한다면 내부적으로 컴파일시 최적화를 해줍니다. Java8에서는 StringBuilder가 최적화를 해주며, Java11의 경우 StringConcatFactory가 최적화를 해줍니다.
Java8에서 자동으로 컴파일시점에 최적화를 해준 StringBuilder는 + 를 할 떄마다 StringBuilder를 생성하여 문자열을 합쳐주고 다시 String으로 반환을 하여 +연산을 할 떄마다 각각의 StringBuilder를 선언을 해주는 단점이 존재하였습니다. 그래서 Java11부터 새로운 방법인 StringConcatFactory로 변경되었습니다. StringBuilderStringConcatFactory의 차이점을 간단히 설명하자면 StringBuilder는 +를 할 때마다 하나의 String객체를 반환하는 반면에 StringConcatFactory는 최종 상태에서만 String을 만들어줍니다.

앞서 말했듯이 JVM이 컴파일 시점에 스스로 String연결을 최적화를 해줍니다. 하지만 최적화는 항상 해주는 것이 아니라 해주지 않는 경우도 존재합니다. 그래서 우리는 긴 문자열들을 더할 때는 StringBuilder, StringBuffer를 사용하여 직접 낭비를 줄일 수 있습니다.

StringBuilder, StringBuffer의 특징과 차이점

  • 둘 다 내부적으로 가변적인 char[]를 멤버 변수로 가집니다.
  • 새로운 인스턴스를 생성하지 않고 char[]를 변경할 수 있어서 문자열을 여러번 연결하거나 변경할 때 사용하면 유용합니다.
  • 출력은 나중에 toString() 메서드로 String반환을 해주면 됩니다.
  • StringBuilder와 StringBuffer는 char[] (character buffer)를 갖는 공통점이 있으나 StringBuffer는 multi-thread환경에서 동기화(synchronization)가 보장됩니다.
  • 그래서 single thread 프로그래밍의 경우는 StringBuilder사용을 권장하며 multi-thread환경에서는 StringBuffer를 사용을 권장한다.

📌 하지만 StringBuffer는 실제로 쓰일 일이 거의 없어 StringBuilder를 사용하는 것이 좋습니다.

사용 예제 코드

public class StringBuilderTest {
    public static void main(String[] args) {
        String string1 = "Rex's ";
        String string2 = "develop diary";

        StringBuilder buffer = new StringBuilder(string1);
        System.out.println(System.identityHashCode(buffer)); //1809787067
        buffer.append(string2);
        System.out.println(System.identityHashCode(buffer)); //1809787067

        System.out.println(buffer.toString()); // Rex's develop diary
    }
}
Buy me a coffeeBuy me a coffee
Written by

@Seongwon

기술공유를 통해 새로운 가치 창조을 추구하는 백엔드 개발자 오성원입니다.
©SeongwonOh