java
[자바] String Class 의 불변성
하루이2222
2024. 8. 18. 19:37
자바의 String
클래스와 참조 주소 변경
1. String
클래스의 불변성(Immutable)
- 불변성: 자바의
String
클래스는 불변 객체이다. 한 번 생성된String
객체는 그 내용을 수정할 수 없다.- 예를 들어,
String str = "Hello";
는"Hello"
라는 문자열을 가리키는str
참조 변수를 생성한다.str
의 값을 변경하려면 새로운String
객체가 만들어져야 한다. - 문자열을 수정할 때마다 새로운
String
객체가 생성되고, 기존의 객체는 그대로 유지된다.
- 예를 들어,
2. 참조 주소의 변경
- 참조 변경: 기존
String
객체를 수정하지 않고, 새로운 객체를 생성하여 참조 변수를 변경한다.- 예시:
String str = "Hello"; str = str + " World";
"Hello"
문자열 객체는 변경되지 않고,"Hello World"
라는 새로운 객체가 생성되어str
이 이를 가리키도록 참조가 변경된다.
- 예시:
3. 배열에서의 참조 주소 변경
배열의 수정 가능성: 배열 요소의 참조를 변경하는 것은 가능하지만, 배열 내의
String
객체 자체는 불변이기 때문에 수정할 수 없다.- 배열의 요소를 다른
String
객체로 교체할 수 있으며, 이 경우 배열의 참조 주소는 변경되지 않지만, 배열의 요소가 가리키는 객체가 변경된다.
- 배열의 요소를 다른
배열 크기 변경: 배열 크기를 변경하려면, 기존 배열을 복사하여 더 큰 배열을 생성하고, 참조를 새로운 배열로 변경해야 한다.
- 예시:
String[] strArray = {"Hello"}; strArray = Arrays.copyOf(strArray, strArray.length + 1); strArray[1] = "World";
- 이 과정에서 새로운 배열이 생성되고,
strArray
가 이를 가리키도록 참조가 변경된다.
- 이 과정에서 새로운 배열이 생성되고,
- 예시:
4. 참조 주소 변경 확인 방법
해시코드 사용: 참조 주소를 직접 확인하는 것은 불가능하지만, 객체의 해시코드를 통해 간접적으로 추적할 수 있다.
System.identityHashCode(Object obj)
메소드를 사용하면 객체의 고유 해시코드를 확인할 수 있다.- 해시코드가 달라지면 참조하는 객체가 변경된 것을 의미한다.
예시 코드:
public class Main { public static void main(String[] args) { String str = "Hello"; System.out.println("Initial HashCode: " + System.identityHashCode(str)); str = str + " World"; System.out.println("After Modification HashCode: " + System.identityHashCode(str)); String[] strArray = {"Hello"}; System.out.println("Initial Array HashCode: " + System.identityHashCode(strArray)); strArray = Arrays.copyOf(strArray, strArray.length + 1); System.out.println("After Array Copy HashCode: " + System.identityHashCode(strArray)); } }
- 이 코드를 통해 문자열이나 배열의 참조가 변경되었는지 확인할 수 있다.
5. 디버깅을 통한 참조 주소 확인
- 인텔리J 디버거 사용:
- 디버거에서 브레이크포인트를 설정하고, 문자열 및 배열의 해시코드를 단계별로 추적하여 참조가 변경되는지 확인할 수 있다.
- 이는 객체의 참조 주소가 변경되었는지 확인하는 효과적인 방법이다.
좀더 근본적인 이유
1. 안전성 (Thread Safety)
설명: 자바의
String
객체는 불변이기 때문에, 생성된 후에 그 내용이 절대 변경되지 않는다. 이는 특히 멀티스레드 환경에서 매우 중요한 특성이다.멀티스레드 환경:
- 여러 스레드가 동일한
String
객체를 공유할 때, 만약 이 객체가 수정 가능(mutable)하다면, 한 스레드가 객체를 변경할 경우 다른 스레드에 영향을 미칠 수 있다. 이는 동기화 문제를 발생시킬 수 있으며, 프로그램의 예측 불가능한 동작을 초래할 수 있다. - 그러나
String
이 불변이므로, 어떤 스레드도String
객체의 내용을 변경할 수 없다. 따라서 여러 스레드가 동일한String
객체를 안전하게 사용할 수 있다. 불변 객체는 이러한 동기화 문제를 회피할 수 있게 해주며, 스레드 간의 상호작용을 단순화한다.
- 여러 스레드가 동일한
예시:
public class StringExample { public static void main(String[] args) { String sharedString = "Hello"; Runnable task = () -> { System.out.println(sharedString); // sharedString은 불변이므로, 어떤 스레드도 이 문자열을 수정할 수 없다. }; Thread thread1 = new Thread(task); Thread thread2 = new Thread(task); thread1.start(); thread2.start(); } }
- 이 예제에서 두 스레드가 동일한
sharedString
을 안전하게 사용할 수 있다.
- 이 예제에서 두 스레드가 동일한
2. 캐싱 (Caching)
설명: 자바에서는 동일한
String
리터럴이 여러 곳에서 사용될 때, 메모리에서 동일한 객체를 재사용한다. 이 메모리 절약 기법을 String interning이라고 한다.String Pool:
- 자바 런타임은
String
리터럴을 내부적으로 String Pool이라는 메모리 영역에 저장한다. 동일한 문자열이 여러 곳에서 사용될 때, 새로운 객체를 생성하지 않고, 이미 생성된 객체를 재사용한다. - 이는 메모리 사용량을 줄이고, 애플리케이션의 성능을 향상시키는 데 기여한다.
- 자바 런타임은
불변성이 필요한 이유:
String
이 불변이기 때문에, 동일한 객체를 여러 곳에서 안전하게 재사용할 수 있다. 만약String
이 가변적이었다면, 객체가 재사용될 때 그 내용을 변경할 수 있었을 것이고, 이는 프로그램의 의도하지 않은 동작을 유발할 수 있었다.
예시:
String str1 = "Hello"; String str2 = "Hello"; // str1과 str2는 동일한 객체를 참조 System.out.println(str1 == str2); // true
- 여기서
str1
과str2
는 동일한 객체를 가리키고 있다. 이는String
의 불변성 덕분에 가능하다.
- 여기서
3. 해시 코드 최적화 (Hash Code Optimization)
설명:
String
객체의 해시코드는 그 내용에 기반하여 계산된다. 불변 객체의 해시코드는 한 번 계산되면 그 후에는 절대 변하지 않는다.해시 기반 컬렉션:
- 자바의
HashMap
,HashSet
등과 같은 해시 기반 컬렉션은 내부적으로 객체의 해시코드를 사용하여 객체를 저장하고 검색한다. 해시코드가 변경되지 않으므로, 이러한 컬렉션에서의 검색 작업이 일관성을 유지하고, 효율적으로 수행될 수 있다.
- 자바의
효율성:
String
이 불변이기 때문에, 해시코드를 여러 번 계산할 필요 없이, 한 번 계산한 해시코드를 캐싱해두고 필요할 때마다 이를 사용할 수 있다. 이는 컬렉션의 성능을 크게 향상시킬 수 있다.
예시:
String str = "example"; int hashCode1 = str.hashCode(); // 어떤 작업을 수행하더라도, str의 해시코드는 변경되지 않음 int hashCode2 = str.hashCode(); System.out.println(hashCode1 == hashCode2); // true
- 이 예제에서
str
의 해시코드는 처음에 계산된 이후로 변경되지 않으며, 해시 기반 컬렉션에서 빠르고 일관된 성능을 제공한다.
- 이 예제에서