본문 바로가기

Programming/Java & Spring 관련 내용 정리

[JAVA] String 클래스에 대해 설명해 주세요.

🎈 스트링 내부를 들여다보면?

스트링 내부를 살펴보면, Char 배열로 생성된 불변객체 값이다.

 

 


🎈 Java에서 String은 불변(Immutable) 객체이다.

불변 객체란, 객체가 생성된 후 내부의 상태가 변하지 않고 계속 유지되는 객체를 말한다. 

변수에 객체가 한 번 할당되면 해당 객체의 참조를 변경할 수도, 내부의 상태를 수정할 수도 없는 것이다.


🎈 왜 String은 불변 객체일까? 

1. String Pool 

String이 불변이기 때문에 String Pool도 존재할 수 있다.

 

어떤 프로그래밍 언어라도 String 타입은 매우 빈번하게 사용된다.

그래서 Java에서는 String Pool이라는 공간에 String을 포함시켜서

매번 String 객체를 새로 생성하기보다는

값이 같은 String이라면 String Pool에 있는 객체를 재사용할 수 있도록 구현했다.

 

값이 같은 String은 String Pool 내에서 String 객체를 공유하도록 한 것이다.

그런데 공유를 하려면 String은 반드시 불변이어야 한다.

 

mutable하다면 두 객체의 공유는 불가능하다.

 

 

위 예시에서 s1과 s2는, "Java"라는 value를 갖는 String Pool 내부의 하나의 String 객체를 바라보고 있다.

이 때 s1의 값을 "C++"로 바꾼다면 s1은 String Pool 내부의 다른 String 객체를 바라보게 된다.

 

하지만 만약 String이 mutable하다면?  s1의 값만 "C++"로 바꿀 수 있고

s2는 그대로 "Java"로 남아있게 되는데

값이 다른데 같은 참조를 가진다는 것은 말이 안된다.

 

String 타입이 mutable하다면, String 객체끼리의 공유는 불가능하게 된다.


실제로 s1을 "C++"로 바꾸면, String Pool에 이미 "C++" 값을 가진 객체가 있으면 그 객체를 참조하고,

없다면 String Pool에 새로운 객체를 생성한다.

 

결국 Java에서는 String pool을 구현하기 위해 String을 immutable한 객체로 만들어야 했던 것이다.

 

2. 보안 

Java에서 메서드의 파라미터로 String을 받는 경우는 매우 흔하다.

그런데 String이 mutable하다면, 메서드의 인자로 받은 값은 메서드의 caller(호출자)에 의해 언제든지 바뀔 수 있게 된다. 

이는 보안상 취약점을 발생시킨다.

3. 동기화

객체가 불변이면 멀티 스레드 환경에서도 값이 바뀔 위험이 없기 때문에

자연스럽게 thread-safe한 특성을 갖게 되고

동기화와 관련된 위험 요소에서 벗어날 수 있다. 

 

또한 String의 경우 한 스레드에서 값을 바꾸면, 해당 객체의 값을 수정하는 것이 아니라

새로운 객체를 String Pool에 생성한다.

따라서 thread-safe하다고 볼 수 있다.

 

4. Hashcode Caching

String의 hashCode() 메서드 구현을 보면,

아직 hash 값을 계산한 적이 없을 때 최초 1번만 실제 계산 로직을 수행한다.

이후부터는 이전에 계산했던 값을 그냥 리턴만 하도록 되어 있다.

즉 hashCode 값을 캐싱(caching)하고 있다.

 

이렇게 caching이 가능한 것도 결국 String이 불변이기 때문에 얻을 수 있는 이점이다.

 

 


🎈 스트링 클래스 선언은?

스트링은 원시 타입처럼 보여서 원시타입으로 착각 할 수 있다. 

하지만 스트링은 원시타입이 아니며 참조형 객체 클래스이다. 

또한 불변 객체로 동일한 객체가 공유되면서 사용된다.

 

  • 스트링 클래스는 2가지의 선언 방법이 있다.
    1. 리터럴
    2. new 를 사용한 선언

 

리터럴로 선언된 String 클래스는 객체가 String constant pool 에 저장된다.

하지만 new 는 Heap 영역에 저장이 된다.

 


🎈 Java String Pool

 

String 리터럴로 생성하면 해당 String 값은 Heap 영역 내 String Constant Pool에 저장되어 재사용되지만,

new 연산자로 생성하면 같은 내용이라도 여러 개의 객체가 각각 Heap 영역을 차지하게 된다.

 

 

🎈 String pool 의 위치

 

String pool 은 java 6 버전까지 Perm 영역이었다.

하지만 Perm 영역은 고정된 사이즈이며 Runtime 에 사이즈가 확장되지 않는다.

 

그래서 intern 되는 String 값이 커지면 OutOfMemoryException을 발생시킬 수 있었고

그에따라 java 7 버전에서 heap 영역으로 String pool 의 위치를 변경하였다.

 


false 가 나오는 이유가 뭘까?


위에서 언급한 리터럴과 new 의 선언 시 저장되는 객체의 위치 때문이다. 

text 과 text2 의 값은 모두 text 라는 값을 가지고 있다.

하지만 객체가 다르다.

 

text String은 constant pool 에 저장된 객체를 갖고 있고 

text2는 Heap 영역에 저장되어있는 객체이다.

 

따라서 1차적으로 객체가 다르기 때문에 단순비교인 == 로는 두 객체의 값을 비교할 수 없다.

 


 

String 클래스는 불변 객체이기 때문에

위와 같은 코드는 str의 값이 바뀐 것이 아니라 

str에 “Hello”와 “World!”를 더한 새로운 String 객체가 재할당 된 것이다.

 

 

 

실제로 아래 예제와 같이 객체의 주소 값을 출력해보면

+ 연산을 하기 전과 후가 다르게 출력하는 것을 볼 수 있다.

 

 

String 객체의 + 연산이 문제가 되는 이유가 바로 여기에 있다.

 

str += "world!"; 연산이 실행되면

앞서 String str = "Hello,";로 초기화했던 str 객체는 버려져 

GC(Garbage Collection)의 대상이 된다.

 

GC는 하면 할수록 시스템의 CPU를 사용하고 시간도 많이 소요되기 때문에 GC의 대상이 되는 객체를 최소화해야 한다. 

 


🎈 StringBuilder, StringBuffer

 

StringBuilder와 StringBuffer는 String 데이터 변경을 위해 자바에서 제공하는 객체이다. 

내부적으로 동적 배열로 구현되어있다.

위에서 살펴봤던 String 객체 + 연산의 문제점을 해결해줄 방법이 바로 StringBuilder와 StringBuffer를 사용하는 것이다. 


두 객체의 append 메서드나 insert 메서드를 사용하면 버려지는 객체 없이 문자열을 더할 수 있다.

StringBuilder와 StringBuffer가 제공하는 기능은 같다.

 

 

그렇다면 두 객체의 차이는 뭘까?
바로 synchronized 키워드를 사용한 동기화 여부에 있다.

 

 

StringBuffer 객체는 거의 모든 메서드를 synchronized를 사용해서 동기화하기 때문에 

멀티 스레드 환경에서 안전하게 사용할 수 있다. 

 

반대로 StringBuilder는 동기화하지 않기 때문에 멀티 스레드 환경에서 안전하지 못하다.

하지만 이것이 안전한 StringBuffer만 사용하면 된다는 의미는 아니다.


synchronized를 사용한 동기화는 lock을 걸고 푸는 오버헤드가 있어서 속도가 느리다.

즉, StringBuilder가 StringBuffer보다 빠르다.

 


🎈결론

 

String + 연산은 문자열을 반복적으로 더하지 않을 경우에만 사용하는 것이 좋다.

 

 

StringBuffer는 다음과 같은 경우에 사용하는 것이 좋다.

 

  1. 멀티 스레드 환경에서 안전한 프로그램이 필요할 때
  2. static으로 선언된 문자열을 변경할 때
  3. singleton으로 선언된 클래스의 문자열을 변경할 때

 

 

StringBuilder는 다음과 같은 경우에 사용하는 것이 좋다.

 

  1. 스레드에 안전한지의 여부와 전혀 관계없는 프로그램을 개발할 때
  2. 메서드 내에서 지역 변수로 StringBuilder를 사용할 때