해당 강의를 보고 정리한 내용입니다.
이번 글에서는 불변 객체의 장점과 java에서 불변 객체를 어떻게 정의할 수 있는 지 살펴보겠습니다.
불변 객체(Immutable Objects)는 객체 생성 이후에 내부 상태가 변경되지 않는 객체를 말합니다. 객체가 생성된 후에는 그 상태를 수정할 수 없습니다.
예를 들어, 직원(Employee) 타입의 객체를 처음 생성할 때 해당 직원의 정보를 설정하고 이후에는 해당 정보를 수정할 수 없는 객체를 불변 객체라고 합니다.
map, set, cache에 쓰기 적절하다
(일반적으로) thread-safe 합니다.
thread-safe하다는 말은 여러 쓰레드가 하나의 객체를 공유해서 사용할 때 데이터 불일치 없이 사용할 수 있다는 말입니다. 왜냐하면 불변 객체는 객체의 상태가 바뀌지 않는 객체이기 때문에 그 안에 상태 정보가 항상 일치된 상태로 유지될 것이기에 여러 쓰레드에서 공유해도 안전하게 쓸 수 있습니다.
불변 객체를 필드로 쓰면 방어적 복사를 할 필요가 없습니다
모든 것을 불변 객체로 표현할 수는 없겠지만 가능하다면 불변 객체를 적극적으로 쓰는 것을 추천합니다.
String은 Java의 대표적인 불변 객체입니다.
String 클래스는 한 번 생성된 이후에 내부 문자열 값이 변경되지 않습니다. 즉, 문자열이 생성된 후에는 그 문자열을 수정하거나 변경할 수 없습니다
불변 객체를 만들기 위해서는 해당 객체의 상태를 생성 이후에 변경할 수 없도록 설계해야 합니다. 불변 객체는 안정성과 예측 가능한 동작을 제공하며, 다양한 상황에서 활용할 수 있는 객체입니다. 이를 위해 다음과 같은 단계로 불변 객체를 만들 수 있습니다.
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName(){
return name;
}
public void setName(String name){
this.name = name;
}
}
해당 코드를 불변 객체로 바꿔보겠습니다.
불변 객체에서는 객체 생성 이후에 내부 상태를 변경하지 않으므로 setter 메서드를 제거합니다.
public class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName(){
return name;
}
}
이제는, 모든 필드를 private final로 지정합니다.
필드가 private final로 지정되면 클래스 외부에서 이를 직접 수정할 수 없습니다.
public class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName(){
return name;
}
}
여기서 끝이 아닙니다.
지금의 경우 다음과 같은 문제가 발생할 수 있습니다.
자녀 클래스의 메서드 override로 인해 다음과 같은 문제가 발생하였습니다.
불변 객체의 메서드가 자식 클래스에서 오버라이드되면 예기치 않은 동작이 발생할 수 있습니다. 이로인해 불변 객체로 만들기 위해서는 자녀 클래스의 메서드 override를 금지해야합니다. 즉, 클래스 상속을 금지합니다.
클래스를 final로 지정하여 상속을 금지합니다.
이 방법 말고도 생성자의 접근제어자를 public -> private으로 바꾸고 static factory method를 통해 객체를 생성하는 방법으로 상속을 하지 못하게 막을 수도 있습니다.
public final class Person {
private final String name;
public Person(String name) {
this.name = name;
}
public String getName(){
return name;
}
}
만약 객체의 필드 중에 mutable 객체를 가리키는 레퍼런스가 있다면 해당 객체를 완벽한 불변 객체라고 볼 수 없습니다.
왜냐하면, 다음과 같은 상황이 발생할 수 있기 때문입니다.
mutable한 RGB 객체를 직접 참조하고 있었기 때문에 발생한 문제입니다.
해당 문제를 해결하기 위해 Person class의 생성자를 다음과 같이 수정할 수 있습니다.
이렇게 하면 모든 문제가 해결됬을까요?
아닙니다. 해당 코드의 경우 다음과 같은 문제가 발생 할 수 있습니다.
RGB 객체를 반환할 때, 자신이 내부적으로 가지고 있던 RGB를 그대로 반환했기 때문에 발생한 문제입니다.
이 문제를 해결하기 위해, getter method를 다음과 같이 수정할 수 있습니다.
불변 객체를 필드로 사용했을 때, 위의 코드와 같이 방어적 복사를 할 필요가 없어집니다.
List와 List에 있는 mutable 객체를 모두 방어적 복사를 해야 합니다.
Immutable 객체는 외부에 노출되는 상태 정보가 변경 불가능한 객체를 의미합니다. 이로 인해 객체가 한 번 생성되고 초기화된 이후에는 내부 상태를 변경할 수 없습니다. 이러한 특성은 객체의 불변성과 안정성을 보장하는 중요한 특징입니다.
그러나 때로는 외부에 노출되지 않는 내부의 상태가 변경 가능한 경우가 있을 수 있습니다. 이런 경우에도 "Immutable 객체"로 부르기도 합니다.
해당 상태는 외부에서는 객체의 상태 변경이 불가능하지만, 내부에서는 여전히 변경 가능한 상태를 가지는 것입니다.
이렇게 내부에서만 변경 가능한 상태를 가진 "Immutable 객체"의 경우, 외부에서는 해당 상태를 관리하거나 수정할 수 없기 때문에 외부에서는 불변성이 보장됩니다. 그러나 내부에서는 변경 가능한 상태를 가지기 때문에 thread-safe 하지 않을 수 있습니다. 여러 스레드에서 동시에 내부 상태를 변경하려고 할 경우 문제가 발생할 수 있습니다.