불변 클래스란 내부 인스턴스 값을 수정할 수 없는 클래스를 말한다. 각 내부 멤버 변수가 모두 고정되며 생성되서 파괴 될 때 까지 변하지 않는다. 대표적인 불변 클래스는 String, 박싱된 클래스, BigInteger, BigDecimal 등이 있다.
4, 5번은 너무 당연한 이야기임으로 패스한다. 그래도 간단히 이야기 하자면 4번과 5번은 캡슐화와 객체가 가져야할 책임 자체와 관련이 깊다. 고로 4, 5는 불변 클래스가 아니더라도 객체를 생성하면 항상 만족해야하는 조건이다.
불변 클래스는 주로 Value Object에 많이 쓰인다. Value Object는 엔티티와 다르게 자기가 가지고 있는 멤버 변수 값들을 이용해 식별한다. 그렇기에 값이 변경되면 안된다. 불변 클래스의 최고의 예제인 것이다.
public final class Complex {
private final double real;
private final double img;
public Complex(double real, double img) {
this.real = real;
this.img = img;
}
public double getReal() {
return real;
}
public double getImg() {
return img;
}
public Complex plus(Complex c) {
return new Complex(real + c.real, img + c.img);
}
public Complex minus(Complex c) {
return new Complex(real - c.real, img - c.img);
}
public Complex times(Complex c) {
return new Complex(real * c.real - img * c.img, real * c.img + img * c.real);
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
Complex complex = (Complex) o;
return Double.compare(complex.real, real) == 0
&& Double.compare(complex.img, img) == 0;
}
@Override
public int hashCode() {
return Objects.hash(real, img);
}
@Override
public String toString() {
return "Complex{" +
"real=" + real +
", img=" + img +
'}';
}
}
Complex는 실수부와 허수부를 동시에 가진 값 객체
라 할 수 있다. 그래서 final 로 상속을 금지하고 final로 멤버 변수 불변 및 equalsAndHashCode를 통해 값을 식별자로 설정한다. 이러면 완벽한 불변 객체를 만들 수 있다.
여기서 중요한 점은 불변객체는 객체를 변화시키지 않기 때문에 사칙 연산에 대해서 매번 새로운 인스턴스 객체를 생성한다.
이것 때문에 불필요한 인스턴스 생성 비용으로 인한 성능 하락은 있을 수 있다. 이를 극복하기 위해 Caching 전략을 활용하기도 한다.
쓰레드 문제는 무엇일까? 다들 한번 쯤 멀티쓰레드를 고려할 떄 동시성 문제를 고려해본 경험이 있을 것이다. 멀티 쓰레드를 활용할 때 객체의 인스턴스 생성 없이 하나의 인스턴스의 가변 인자를 동시에 수정하려고 접근하면, 각 쓰레드가 병렬로 가변 객체를 읽는 과정에서 잘못 읽어 올 수 있다. 그래서 수정이 누락되는 문제가 발생하는데 이를 동시성 문제라 한다.
불변 객체는 애초에 가변 인자가 없으므로 이 문제에 대해서 안전하다.
BigInteger 클래스는 내부에서 값의 부호와 크기를 따로 표현한다. 부호는 int, 크기(절대값)은 int 배열을 활용한다. 이때 배열은 비록 가변ㅇ지만 복사하지 않고 원본 인스턴스와 공유해도 전혀 문제가 없다.
이는 Value Object와 큰 관계가 있다. 이들은 Set이나 Map과 같은 컬렉션 뿐만 아니라 다른 클래스의 구성요소로 써도 사이드 이펙트가 전혀 없기 때문에 사용하기 편하다.
실패 원자성(failure atomicity)란 메서드에서 예외가 발생한 후에도 그 객체는 여전히 유효한 상태이다. 상태 자체가 절대 변하는 일이 없으니 잠깐 동안 예외가 발생하더라도 불일치 상태에 빠질 가능성이 전혀 없다.
불변은 사이드 이펙트를 줄이는 가장 좋은 방법이다. Intellij를 활용한다면 해당 멤버 변수가 수정 될 일이 없다면 알아서 final을 추천해준다. 큰 이유가 없다면 항상 private final로 선언하자.