-
[Java] Immutable 클래스와 String 클래스Java 2024. 7. 8. 14:25
자바에는 불변 클래스라는 개념이 있다. 불변 클래스란 객체의 상태가 한 번 생성된 이후에는 변경될 수 없는 클래스를 말한다. 대표적으로 String 클래스가 불변 클래스 중 하나이다.
불변 클래스의 특성은 다음과 같다.
1. 객체의 상태는 생성 시에만 설정되며 이후에는 변경되지 않는다.
2. 모든 필드는 private final로 선언되어야 한다.
3. 클래스 자체도 final로 선언된다. (해당 클래스 상속 불가)
불변 클래스의 이러한 특징으로 인해 다음과 같은 장점을 가진다.
1. 객체의 상태가 변경되지 않기 때문에 멀티 쓰레드 환경에서 안전하다.
2. 상태 변화가 없으므로 복잡성이 줄어든다.
3. 동일한 값으로 여러 객체를 생성하는 대신 하나의 객체를 재사용할 수 있어 메모리 효율이 향상된다.
자바의 대표적인 불변 클래스인 String 클래스에 대해 알아보자. String 클래스가 불변 클래스로 설계된 이유는 보안과 관련이 있다. 자바로 작성된 많은 API 메서드는 String을 사용하여 파일의 이름, 네트워크 연결, 사용자 인증 정보를 다룬다. 만약 String 클래스가 변경될 수 있다면 보안상 문제가 발생할 수 있다.
*문자열 비교 시 equals() 메서드를 사용해야 하는 이유*public class Study { public static void main(String[] args) { String str1 = "hello"; String str2 = "hello"; System.out.println("result = " + (str1 == str2)); // true System.out.println("result = " + (str1.equals(str2))); // true } }
위 코드에서 str1, str2는 같은 메모리를 사용한다. String을 쌍따옴표("")를 사용하여 생성할 때 이미 해당 문자열이 존재하는 경우 메모리를 새로 사용하지 않고 기존 메모리를 사용한다. 따라서 주소 값을 비교하는 동등 비교 연산자의 결과도 참이고, 문자열 자체를 비교하도록 오버라이드된 equals() 메서드의 결과도 참인 것이다.
하지만 다음 경우엔 어떨까?public class Study { public static void main(String[] args) { String str3 = new String("hello"); String str4 = new String("hello"); System.out.println("result = " + (str3 == str4)); // false System.out.println("result = " + (str3.equals(str4))); // true } }
String 객체를 new 연산자를 통해 생성하면 문자열이 같더라도 항상 새로운 메모리 영역을 사용한다. 쌍따옴표를 사용하는 방식보다 메모리를 비효율적으로 사용한다는 것을 알 수 있다. 항상 새로운 메모리를 사용하기 때문에 동등 비교 연산자의 결과가 거짓이 나오는 것을 확인할 수 있다. 반면에 String의 equals() 메서드는 항상 문자열 자체만 비교하기 때문에 결과는 참이다. 따라서 문자열 비교 시 항상 equals() 메서드를 사용해야 한다.
*String 객체 생성 시 메모리 사용*
- 문자열 리터럴 (쌍따옴표) 사용 : String Constant Pool에 저장된다. 동일한 문자열 리터럴은 동일한 메모리 위치를 공유한다.
- new 연산자 사용 : heap 메모리 영역에 늘 새로운 String 객체를 생성한다. 동일한 문자열이더라도 각기 다른 메모리 위치를 차지한다.
다음 코드를 보자.public class Study { public static void main(String[] args) { String str = "hello"; str = "HELLO"; } }
변수 str에 "hello"를 할당하고 "HELLO"로 재할당하면 어떻게 될까? str이 기존에 가리키고 있던 0x100번지의 값이 "HELLO"로 바뀌지 않고 새로운 메모리(0x200)를 사용한다. 0x100번지의 "hello"는 계속 상수 풀에 남아있게 된다. 따라서 문자열 조작이 잦을 때는 String 대신 StringBuilder 또는 StringBuffer를 사용하는 것이 메모리 측면에서 더 효율적이다.public class Study { public static void main(String[] args) { String str = "Hello"; for (int i = 0; i < 1000; i++) { str += " World"; // 매번 새로운 String 객체가 생성됨 } System.out.println(str); StringBuilder sb = new StringBuilder("Hello"); for (int i = 0; i < 1000; i++) { sb.append(" World"); // 동일한 StringBuilder 객체 내에서 변경됨 } System.out.println(sb.toString()); } }
*StringBuilder와 StringBuffer*
StringBuilder와 StringBuffer는 String과 같이 문자열을 다루는 데 사용하는 클래스이다. String과 차이점은 이 둘은 가변(mutable) 객체라는 것이다. 그렇기 때문에 문자열을 자주 조작하는 경우 사용하면 String보다 좋은 성능을 발휘한다.
StringBuilder와 StringBuffer는 멀티 쓰레드 환경에서 중요한 차이가 있다. StringBuilder는 멀티 쓰레드에 안전하지 않지만 성능이 더 우수하여 단일 쓰레드 환경에서 많이 사용된다. StringBuffer는 멀티 쓰레드 환경에서 안전하도록 동기화되어 있어 멀티 쓰레드 환경에서 주로 사용된다.
*결론*
1. String 객체를 생성할 때는 문자열 리터럴("")을 사용하자.
2. 문자열 비교 시 equals()를 사용하자.
3. 문자열 조작이 잦을 시 StringBuilder 또는 StringBuffer를 사용하자.'Java' 카테고리의 다른 글
[Java] JVM의 메모리 영역 (JVM Memory Structure) (0) 2024.10.29 [Java] Comparable과 Comparator (0) 2024.10.25 [Java] 자바 직렬화에 대해서 (Serialization) (2) 2024.10.23 [Java] parseInt() 메서드 직접 구현하기 (0) 2024.09.04 [Java] 오버로딩의 제약 조건 (1) 2024.06.30