String Class에 대해서 (불변 객체,StringBuilder)

Do_It·2024년 4월 8일

자바에서 문자를 다루는 대표적인 타입은 char , String 2가지가 있다. 기본형인 char는 문자 하나를 다룰 때 사용한다. char를 사용해서 여러 문자를 나열하려면 char[ ]을 사용해야 한다. 하지만 char[ ] 을 직접 다루는 방법은 매우 불편하기 때문에 자바는 문자열을 매우 편리하게 다룰 수 있는 String 클래스를 제공한다.

String을 통해 문자열을 생성하는 2가지 방법

String srt1 = "hello";
String str2 = new String("hello");

String은 클래스이다. 기본형이 아니라 참조형이라는 것이다. str1의 변수에는 인스턴스의 참조값만 들어갈 수 있는데, 문자열은 매우 자주 사용됨으로 편의상 쌍따옴표로 문자열을 감싸면 자바 언어에서 "hello"를 new String("hello")로 변경해준다.

String 클래스 구조

String도 클래스이기 때문에 필드와 메서드를 갖는다.

  • 필드 : private final byte[ ] value;
  • 메서드 : length, charAt,trim 등

String 클래스와 참조형

String 클래스는 기본형이 아니라 참조형이다. 참조형은 변수에 계산할 수 있는 값이 들어있는 것이 아니라 x001과 같이 계산할 수 없는 참조값들이 들어있다. 따라서 원칙적으로 + 같은 연산을 할 수 없다. 하지만 문자열은 너무 자주 다루어지기 때문에 자바 언어에서는 편의상 특별히 + 연산을 제공한다.

String a = "hello";
String b = " java";

System.out.println(a+b); // hello java

String 클래스 - 비교

String 클래스를 비교할 때는 == 비교가 아니라 항상 equals() 비교를 해야한다.

String str1 = new String("hello"); // x001
String str2 = new String("hello") // x002
str1 == str2 // false 참조 값이 다르기 때문
str1.equals(str2); // true

String str3 = "hello"; // x003
String str4 = "hello"; // x003
str3 == str4 // true 문자열 풀에 같은 인스턴스 참조값을 가리키므로 참이다.
str3.equals(str4) // true

String str3 = "hello"; 와 같이 문자열 리터럴을 사용하는 경우 자바는 메모리 효율성과 성능 최적화를 위해 문자열 풀을 사용한다. 자바가 실행되는 시점에 클래스에 문자열 리터럴이 있으면 문자열 풀에 String 인스턴스를 미리 만들어둔다. 이 때 같은 문자열이 있으면 새로운 인스턴스를 만들지 않는다.
String str3 = "hello"와 같이 문자열 리터럴을 사용하면 문자열 풀에서 "hello"라는 문자를 가진 String 인스턴스를 찾는다. 그리고 인스턴스의 참조값을 반환한다. String str4 = "hello";의 경우 "hello" 문자열 리터럴을 사용하므로 문자열 풀에서 str3와 같은 x003 참조를 사용한다. 문자열 풀 덕분에 같은 문자를 사용하는 경우 메모리 사용을 줄이고 문자를 만드는 시간도 줄어들기 때문에 성능도 최적화 할 수 있다.

문자열 풀이 있는 이유

풀은 자원이 모여있는 곳을 의미한다. 프로그래밍에서 풀은 공용 자원을 모아둔 곳을 뜻한다. 여러 곳에서 함께 사용할 수 있는 객체를 필요할 때 마다 생성하고, 제거하는 것은 비효율적이다. 대신 문자열 풀에 필요한 String 인스턴스를 미리 만들어두고 여러곳에서 재사용할 수 있다면 성능과 메모리를 더 최적화 할 수 있는 것이다.
참고로 문자열 풀은 힙 영역을 사용하며 문자열 풀에서 문자를 찾을 떄는 해시 알고리즘을 사용하기 때문에 매우 빠른 속도로 원하는 String 인스턴스를 찾을 수 있따.

그럼 왜 문자열 비교시 ==동일성 비교가 아니라 equals동등성 비교를 하는게 좋은것일까??

왜냐하면 리터럴로 만든 경우가 있을 수 있고 new String() 생성자로 만드는 경우가 있을 수 있기 때문이다.

동일성 비교로 두 String 인스턴스를 비교하는 메서드가 있다하면
private static boolean isSame(String a, Stirng b){
return a==b;
}

isSame(str1,str2) // false
isSame(str3,str3) // true

이렇게 될 수 있으므로 따라서 Sring 인스턴스 비교시에는 항상 equals를 사용해야한다.

String 객체는 불변 객체

String 클래스를 보면 필드가 private final byte[ ] value;로 되어 있음을 알 수 있다. 즉, String 인스턴스가 생성되면 불변 객체로 생성됨을 알 수 있다. 왜 String 인스턴스를 불변 객체로 만든걸까? 만약 String 인스턴스가 불변객체가 아니라면 문자열 풀에 있는 인스턴스 값이 중간에 바뀔 경우 같은 문자열을 참고하는 다른 변수의 값도 함께 변경될 수 있기 때문이다. 즉, 의도치 않은 사이드 이펙트가 발생할 수 있기 때문이다. 따라서 String 인스턴스를 불변 객체로 만들어 추후 사이드 이펙트가 발생하지 않도록 만든 것이다.

StringBuilder 가변 String

String 객체가 불변인 것이기 사이드 이펙트를 막을 수 있는 장점이 있지만, 문자들을 더 해야만 하는 경우 단점이 될 수도 있다. 왜냐하면 불변인 String 객체는 내부 값을 변경할 수 없기 때문이다. 따라서 변경된 값을 기반으로 새로운 String 객체를 생성한다. 객체를 생성할 때마다 자원을 낭비하게 되는 것이다.

이 문제를 해결하기 위해 불변이 아닌 가변 String 객체를 생성할 수 있는 StringBuilder 클래스를 쓰면 된다. 물론 가변의 경우 사이드 이펙트에 주의해서 사용해야 한다.

그래서 StringBuilder는 보통 문자열을 변경하는 동안만 사용하다가 문자열 변경이 끝나면 안전한(불변) String으로 변환하는 것이 좋다.

profile
오늘의 노력이 내일의 성장으로 이어지고 있음을

0개의 댓글