1. String 은 불변인가요?
그렇다. String 은 불변이라는 사실은 자바의 기초에서 다룬다. String 은 불변이고 변경될 수 없다. 하지만 왜 불변이고 특성이 무엇일까?
1.1 String 이 불변인 이유
보안
Stirng 은 class loader, 데이터베이스 연결 정보, 파일 경로, 비밀번호 등 어디에서나 사용된다. 만약 문자열이 변경가능하다면 다음과 같은 이슈가 발생할 수 있다.
- 비밀번호 문자열을 설정했는데, 다른 스레드가 도중에 값을 바꿔버린다면?
- 클래스 로더가 "java.lang.System"이라는 이름을 받았는데, 실행 도중에 "java.lang.Hacker"로 변해버린다면?
해시맵 안정성
String 은 해시맵의 키로 사용될 수 있다. 만약 String 이 변경 가능하면 해시코드 또한 변경될 수 있다는 얘기다. 반대로, 불변으로 설정함으로써 해시코드 또한 변경할 수 없고 String 은 해시맵의 키로 사용될 수 있다.
스레드 안정성
멀티 스레드 환경에서 race condition 에 대한 걱정 없이 String 을 사용할 수 있다. 추가적인 lock 이 필요없다.
String pool
자바는 스트링풀을 사용해서 같은 String 에 대해 재생성을 하지 않고 재사용한다. 만약 String 이 변경가능하다면 참조하는 모든 값이 변경되므로 의도치 않은 에러가 발생한다.
1.2 String pool
String Pool 은 Java에서 문자열 리터럴을 저장하여 중복 생성을 방지하고 메모리 사용을 최적화하는 힙(Heap) 메모리 영역의 특별한 공간이다.
문자열 리터럴로 문자열을 생성하면 먼저 스트링 풀에서 같은 문자열이 있는지 확인하고, 있다면 해당 문자열 객체의 참조를 반환하며, 없다면 새로 생성하여 스트링 풀에 추가 후 참조를 반환한다.
그럼 다음 코드를 보자
String a = "Java";
String b = new String("Java");
System.out.println(a == b);
해당 코드의 결과는 "false" 이다. 그 이유는 "Java" 는 string pool 로 들어가지만 new String("Java")
는 힙 영역의 새로운 객체로 생성된다. 따라서 a 와 b 의 참조값이 다르므로 false 가 출력된다.
반면
a.equals(b)
는 true 가 출력된다. equals 는 "동등성"을 비교하는데, String.equals() 는 자신의 byte[] 값을 순회하면서 하나씩 비교하기 때문이다.
만약 동일하게 만들고 싶다면 intern()
을 사용하면 된다. 해당 메서드로 new 로 생성된 String 을 String pool 에 넣는다.
String a = "Java";
String b = new String("Java");
b = b.intern();
System.out.println(a == b); //true
2. 가변 String 은 StringBuilder 사용
다음과 같은 경우를 보자
public String concatAll(String a, String b, String c, String d) {
return a + b + c + d;
}
concatAll("String", "is", "immutable", "class");
여기서 생성되는 String 은 몇 개인가? 답은 7개이다. 우선 concatAll()
을 호출하기 위한 파라미터로 String
, is
, immutable
, class
가 각각 생성된다.
그리고 concatAll 에서 바로 String is immutable class
가 생성되는 게 아니다. 다음 순서로 생성된다.
- a + b :
String is
생성 String is
+ c :String is immtable
생성String is immatable
+ d :String is immutable class
하지만 최종적으로 필요한 값은 마지막 String is immutable class
이고 이전의 2개는 필요가 없고 곧바로 GC 의 대상이 된다. 이런 비효율을 없애기 위해 가변적인 String 으로 StringBuilder 를 사용한다.
2.1 StringBuilder
위 코드는 아래와 같이 만들 수 있다.
public String concatAll(String a, String b, String c, String d) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder
.append(a)
.append(b)
.append(c)
.append(d);
return stringBuilder;
}
concatAll("String", "is", "immutable", "class");
concatAll()
메서드 안에서는 stringBuilder 객체만 하나 생성될 뿐이다. 따라서 변경이 필요한 값은 String 을 사용하는 것보다 StringBuilder 가 메모리 사용 측면에서 효율적이다.
a + b + c + d;
와 같은 코드는 컴파일될 때 자동으로 StringBuilder 로 치환된다.
2.2 StringBuffer
StringBuffer 는 StringBuilder 와 같은 메서드 (append 등)를 제공하는 가변 문자열 클래스다.반면 동기화 블럭(syncronized)를 통해 멀티스레드 환경에서도 데이터 무결성을 보장한다.
따라서 여러 스레드가 동시에 접근하더라도 데이터를 안전하게 다룰 수 있지만 동기화로 인해 성능이 약간 떨어질 수 있다.