Java String
1. String
String은 다음 인터페이스를 모두 구현한다.
- Serializable: 직렬화
- Comparable: 값 비교
- CharSequence: 문자 시퀀스
CharSequence
Characters의 문자 시퀀스를 표현할 때 사용한다. 다음 세 가지 클래스가 해당 인터페이스를 구현한다.
- String
- StringBuffer
- StringBuilder
일반적으로 프로그래밍 언어에서 String은 Character의 시퀀스다. 그런데 자바의 String은 캐릭터의 시퀀스를 표현하는 '객체'다.
2. How to create String obj?
- String literal
- new Keyword
1) String Literal
String s1 = "welcome";
String s2 = "welcome";
String 객체를 리터럴로 생성하는 예시다. 위 코드가 실행될 때 JVM은 Heap의 String constant pool을 확인하고, 이미 존재하면 새롭게 생성하지 않는다. (메모리 효율성을 위함임)
2) new Keyword
String s3 = new String("Welcome");
위 코드는 new 키워드를 이용해서 String 객체를 생성한다. 해당 방법은 리터럴 방식과 구조적으로 차이가 존재한다. new keyword로 스트링 객체를 생성하면 s3는 String constant pool에 위치하고 있는 스트링 상수가 아닌, Normal-Heap에 위치하고 있는 참조 변수를 할당 받는다. 그리고 해당 참조 변수가 String constant pool에 위치하고 있는 Welcome 상수의 주소를 가지고 있다. 그러므로 String의 euqals 메서드와 "==" 오퍼레이터로 비교할 때 그 결과가 다르다.
equals method
boolean result1 = s1.equals(s2);
boolean result2 = s1.equals(s3);
// result1 is true
// result2 is true
equals 메서드는 해당 변수가 바라보는 객체를 직접 비교한다. s1, s2, s3 최종적으로 String constant pool에 위치한 "Welcome" 상수를 가리키므로 true다.
"==" Operator
boolean result1 = s1 == s2;
boolean result2 = s1 == s3;
// result1 is true
// result2 is false
하지만 == 오퍼레이터의 결과는 다르다. s1, s2 두 변수 모두 String constant pool의 Welcome 상수를 직접 가리키지만, s3는 String constant pool의 Welcome을 가리키고 있는, Normal-Heap(Non-pool) 영역에 위치한 변수를 할당 받기 때문이다. String 객체 생성 시 객체를 저장하는 영역과 String constant pool은 별도의 영역이다. (String constant pool도 Heap 메모리 영역을 사용하지만, 별도의 공간을 할당 받는다.)
3. Why Java use immutable String?
자바의 String은 final 클래스이며, String 객체는 변경할 수 없다.
String s = "Sachin";
s.concat("Tendulkar);
// s is still "Sachin"
String 객체를 생성하고 concat 메서드로 추가 문자열을 더했다. 하지만 s의 결과는 변하지 않는다.
String의 concat 메서드를 사용해서 문자열을 이어붙이면 String constant pool에 위치하고 있는 문자열 객체를 수정하지 않는다. 새롭게 생성할 뿐이다. (더이상 사용하지 않는 경우 Garbage collector가 자원을 회수한다.) 즉 String constant pool에는 두 문자열이 모두 존재한다. 또한 s변수는 여전히 기존 리터럴을 가리키고 있는 상태라 값이 변하지 않는다. 그러므로 의도한 코드가 의도한 대로 동작하려면 s의 참조값을 바꿔주어야 한다. 이것이 자바의 String이 불변 객체이며 내부 동작 메커니즘을 보여주는 예시다.
Why?
왜 스트링 객체를 변경하지 못하도록 설계했는가? 자바는 객체지향 프로그래밍 언어다. 예시로 5개의 참조 변수가 있을 때, 1개의 참조 변수가 참조 중인 객체의 값을 변경하면 다른 4개의 참조 변수가 모두 영향을 받아야 한다. 또한 다음과 같은 자바의 특징이 String 클래스가 변경 불가능한 객체가 되도록 만든 이유다.
- ClassLoader: 클래스 로더는 아규먼트로 String 객체를 사용한다. 만약 변경이 가능하면 엉뚱한 클래스를 로드할 수도 있다.
- Thread safe: 멀티 스레드에서 객체를 공유할 때 유용하다.
- Security: 문자열을 변경하지 못하므로 보안 측면에서 더 유리하다.
- Heap space: String constant pool을 재사용함으로써 메모리를 더욱 효율적으로 사용할 수 있다.
4. StringBuffer& SringBuilder
스트링버퍼, 스트링빌더는 가변 문자열 객체를 생성하는 데 사용한다. 변경할 수 있다는 점을 제외하고는 스트링 객체와 동일하다. 그런데 앞서 스트링은 불변 객체라고 했는데, 왜 갑자기 스트링버퍼와 빌더는 가변 문자열을 다룰 수 있다는 것일까?
- 스트링버퍼와 빌더는 모두 내부에 스트링 버퍼를 배열(char[])로 가지고 있다.
- 스트링버퍼와 빌더는 String constant pool이 아닌 normal heap 영역을 사용한다.
- 스트링버퍼와 빌더는 문자열 변경 시 기존 문자열 객체를 수정하는 것이 아니라 새롭게 생성하고 재참조 시킨다.
즉 스트링버퍼, 빌더는 가변 문자열 작업을 위해 지원하는 클래스이며, 스트링은 불변 문자열을 위한 클래스 정도로 정리할 수 있다. (가변 문자열 작업에서 StringBuffer, StringBuilder를 사용하는 것이 String 클래스 보다 훨씬 빠른 속도를 낸다.)
StringBuffer vs StringBuilder
그런데 스트링버퍼와 스트링빌더는 목적이 같은데 왜 두 개가 존재할까?
- StringBuffer: Thread safety 보장
- StringBuilder: 멀티쓰레드 환경에서 공유 변수 조작시 데이터 정합성 문제 발생 가능
가장 큰 차이점은 멀티쓰레드 환경에서 공유 변수 조작에 대한 데이터 정합성을 보장하느냐의 차이다. 따라서 성능 자체만 놓고 본다면 StringBuilder가 더 빠르다.
Reference
https://www.javatpoint.com/java-string
Java String - javatpoint
Java String class with methods such as concat, compareTo, split, join, replace, trim, length, intern, equals, comparison, substring operation.
www.javatpoint.com