가변 객체(mutable Object)란?
가변 객체는 Java에서 Class의 인스턴스가 생성된 이후에 내부 상태가 변경 가능한 객체이다. 멀티 스레드 환경에서 사용하려면 별도의 동기화 처리가 필요하며, 대표적인 가변 객체로 ArrayList, HashMap, StringBuilder, StringBuffer 등이 존재한다. 이 외에도 개발자가 커스텀 객체를 생성하여 내부 상태를 변경할 수 있게 만든다면 그것도 가변 객체가 된다.
불변 객체(Immutable Object)란?
불변 객체란 객체 생성 이후 내부의 상태가 변하지 않는 객체이다. 불변 객체는 read-only 메서드만을 제공하며, 객체의 내부 상태를 제공하는 메서드를 제공하지 않거나 방어적 복사(defensive-copy)를 통해 제공한다.
Java의 대표적인 불변 객체로는 String, wrapper class(Integer, Double, Long, Character, Boolean, Byte, Short, Float) 등이 있다. 불변 객체는 참조 타입이기 때문에 heap 영역에 생성 된다.
String str = "abc"; // 1
str = "string"; // 2
str = "zzz"; // 3
위의 코드를 보자. String은 불변 객체이다. 생성 이후 내부의 상태가 변하지 않는다.
1번 코드에서 "abc" 객체가 생성되고 str은 "abc"를 바라본다.
2번 코드에서 "string"이 생성되고 str은 "string"을 바라보게 된다.
3번에서는 새로운 "zzz"를 바라보게 되고, "abc"와 "string"은 객체가 참조되고 있지 않은 상태인 Unreachable로 구분되어 GC의 대상이 된다.
즉, 가장 처음의 "abc"가 "string"으로 수정되고, 다시 "zzz"로 수정되는 것이 아닌, 계속해서 새로운 객체가 생성되고 str은 새로 생겨난 객체의 주솟값을 참조하는 것이다.
그렇다면 아래의 코드처럼 문자열을 더해주는 경우에는 어떻게 동작할까?
String str = "a";
System.out.println(str); // a
str = str.concat("bc");
System.out.println(str); // abc
더해주는 거니까 기존의 a에 bc를 더해서 heap 영역에는 abc만 있는 거 아닌가?
결론부터 말하자면 a와 abc 둘 다 다른 객체로 존재하게 된다.
String 클래스 내부의 concat() 메서드를 살펴보면 StringConcatHelper.simpleConcat(this, str)를 리턴해주고 있다.
StringConcatHelper.simpleConcat()을 보도록 하자.
리턴 값에 new String()이 있는 것을 확인할 수 있다. 기존의 String 객체를 변경하는 것이 아닌 새로운 String 객체를 반환하는 것이다.
즉, heap 영역 안에는 이렇게 두 개의 String 객체가 존재하고 있다.
때문에 문자열 연산이 많을 경우 String을 사용하게 된다면 매번 새로운 객체를 만들어내므로 성능 저하 문제가 발생할 수 있다. 문자열 연산이 많을 경우 StringBuilder나 StringBuffer를 사용하는 것을 권장한다.
불변 클래스 규칙
- 클래스를 final로 선언
- 모든 클래스 변수를 private와 final로 선언
- 객체를 생성하기 위한 생성자 또는 정적 팩토리 메서드 추가
- 참조에 의해 변경 가능성이 있는 경우 방어적 복사를 이용하여 전달
Java에서는 불변성을 확보할 수 있도록 final 키워드를 제공하고 있다. Java에서 변수들은 기본적으로 가변적인데, 변수에 final 키워드를 붙이면 참조값을 변경 못하도록 하여 불변성을 확보할 수 있다.
아래의 코드처럼 final이 붙은 변수의 값을 변경하려고 하면 컴파일 에러가 발생한다.
final String name = "Lee";
name = "Kim"; // 컴파일 에러
하지만 final 키워드가 객체 내부의 상태를 변경하지 못하도록 하는 것은 아니다. 예를 들어 아래와 같이 final로 선언된 List에는 새로운 객체가 추가되어도 문제가 없다. 그렇게 때문에 Java에서는 참조에 의해 값이 변경될 수 있는 점들을 유의해야 하는데, 이를 방지하려면 불변 클래스로 만들어야 한다.
final List<String> list = new ArrayList<>();
list.add("abc");
불변 클래스 예시
public class Person {
private final String name;
private final int age;
private final List<String> list;
public Person(String name, int age) {
this.name = name;
this.age = age;
this.list = new ArrayList<>();
}
public static Person of(String name, int age) {
return new Person(name, age);
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public List<String> getList() {
return Collections.unmodifiableList(list);
}
}
내부 생성자를 만드는 대신 객체의 생성을 위해 정적 팩토리 메서드를 제공하며, 참조를 전달하여 클라이언트에 의해 수정 가능성이 있는 list를 방어적 복사를 하여 제공한다.
Java에서는 명시적으로 생성자를 선언하지 않으면 기본 생성자가 자동으로 생성되는데, 그러면 다른 클래스에서 해당 객체를 자유롭게 호출할 수 있기 때문에 내부 생성자를 만드는 대신 정적 팩토리 메서드를 통해 객체를 생성하도록 강요하는 것이 좋다. 또한 배열이나 다른 객체 또는 컬렉션은 참조가 전달되어 수정 가능성이 있으므로, 참조를 통해 변경 가능한 경우에는 방어적 복사를 통해 값을 반환해야 한다. 마지막으로 클래스의 변수에 가능하다면 final을, final이 불가능하다면 setter를 최소화하도록 하자.
그렇다면 불변 객체를 왜 사용할까?
- Thread-Safe 하여 병렬 프로그래밍에 유용하며, 동기화를 고려하지 않아도 된다.
- 불변 객체는 여러 스레드에서 동시에 사용해도 변경할 필요가 없기 때문에 스레드 안전성을 보장한다.
- 동기화 작업 없이도 여러 스레드에서 안전하게 사용할 수 있어 병렬 프로그래밍에서 이점을 가진다.
- 실패 원자적인(Failure Atomic) 메서드를 만들 수 있다.
- 가변 객체를 통해 작업을 하던 도중 예외가 발생하면 해당 객체가 불안정한 상태에 빠질 수 있고, 불안정한 상태를 갖는 객체는 또 다른 에러를 유발할 수 있다. 하지만 불변 객체는 어떠한 예외가 발생하여도 메서드 호출 전의 상태를 유지할 수 있다.
- Cache나 Map 또는 Set 등의 요소로 활용하기에 적합하다.
- 만약 Cache나 Map, Set 등의 원소인 가변 객체가 변경되었다면 이를 갱신하는 등의 부가 작업이 필요할 것이다. 하지만 불변 객체라면 한 번 데이터가 저장된 이후에 다른 작업들을 고려하지 않아도 되므로 사용하는 데 용이하게 작용할 것이다.
- 부수 효과(Side Effect)를 피해 오류 가능성을 최소화할 수 있다.
- 부수 효과란 변수의 값이나 상태 등의 변화가 발생하는 효과를 의미한다. 만약 객체의 Setter를 통해 여러 객체들에서 값을 변경한다면 객체의 상태를 예측하기 어려울 것이다.
- 하지만 불변 객체는 기본적으로 값의 수정이 불가능하기 때문에 변경 가능성이 적으며, 객체의 생성과 사용이 상당히 제한된다. 그렇기 때문에 메서드들은 자연스럽게 순수 함수들로 구성될 것이고, 다른 메서드가 호출되어도 객체의 상태가 유지되기 때문에 안전하게 객체를 다시 사용할 수 있다.
- 불변 객체는 오류를 줄여 유지 보수성이 높은 코드를 작성하도록 도와줄 것이다.
- 예측이 가능하여 안전하게 사용할 수 있다.
- 불변 객체는 생성 이후에 내부 상태가 변경되지 않기 때문에 예측 가능한 동작을 제공한다.
- 이는 버그를 줄이고 코드의 신뢰성을 높인다.
[Reference]
'Development > Java' 카테고리의 다른 글
자바 가상 머신 JVM(Java Virtual Machine) (0) | 2024.12.23 |
---|---|
가비지 컬렉션(GC, Garbage Collection) 알고리즘 (0) | 2024.12.17 |
가비지 컬렉션 (GC, Garbage Collection) (0) | 2024.12.17 |