[Effective Java] Item 15: 변경을 최소화하라
불변 클래스는 다음과 같은 5가지 룰을 따라야 한다 .
- 객체의 상태를 변경하는 메소드를 제공하지 않는다.
- 클래스가 상속되지 않음을 명시한다. final 키워드를 클래스를 선언하면 상속되는 것을 막을 수 있다. 자식클래스가 객체의 상태를 바꿀 수 있기 때문에 상속을 금지 시킨다.
- 모든 필드를 final로 선언한다. 이 경우 동기화없이 한 쓰레드에서 다른 쓰레드로 객체를 넘겨도 안전하다.
- 모든 필드를 private로 선언한다. 이 필드로 mutable 객체를 참조하거나 직접 필드에 접근하는 것을 막아준다. 기술적으로 불변 클래스가 원시값이나 불변 객체를 참조하는 public final 필드를 가질 수가 있지만, 이 필드의 값을 변경할 수가 없기 때문에 그다지 추천할만한 방법은 아니다.
- 변경 가능한 요소는 배타적인 접근을 한다. 필드가 mutable 객체를 갖고 있다면, 클라이언트가 이 필드에 접근하지 못하도록 한다.
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
// Accessors with no corresponding mutators
public double realPart() { return re; }
public double imaginaryPart() { return im; }
public Complex add(Complex c) {
return new Complex(re + c.re, im + c.im);
}
public Complex subtract(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex multiply(Complex c) {
return new Complex(re * c.re - im * c.im,
re * c.im + im * c.re);
}
public Complex divide(Complex c) {
double tmp = c.re * c.re + c.im * c.im;
return new Complex((re * c.re + im * c.im) / tmp,
(im * c.re - re * c.im) / tmp);
}
}
위의 클래스는 복소수를 나타내는 클래스이다. 이 클래스의 사칙연산은 모두 새로운 객체를 리턴하는데 이를 가리켜 functional approach라고 부른다. 피연산자의 값을 변경하지 않고 그 값에 어떤 function을 주어 리턴하기 때문이다.
불변 클래스는 태생적으로 쓰레드에 안전하므로 동기화를 할 필요가 없다. 그래서 불변 객체를 사용하는 것이 동기화를 구현하는 가장 쉬운 방법이며 이를 통해 안전하게 객체들을 공유할 수가 있다. 이러한 이유로 불변 클래스로 쉽게 기존 인스턴스를 재사용할 수 있다. 다음과 같이 선언하면 불변 인스턴스의 재사용이 가능하다 .
public static final Complex ZERO = new Complex(0, 0);
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
모든 boxing 원시 타입과 BigInteger이 이와 같이 사용될 수 있으며, 이를 통해 memory footprint와 garbage collection의 비용이 절감된다. 불변 클래스에서는 clone이나 copy 메소드가 필요가 없다. 불변 객체의 인스턴스 뿐만 아니라 그 내부 값들도 공유가 가능하다. BigInteger의 경우 내부 배열 값은 모든 객체에 공유가 된다. 또한 불변 객체는 다른 객체들의 빌딩 블록이 된다. 하부 구조가 변하지 않기에 복잡한 객체의 불변성을 유지하기가 쉬워진다. 불변 클래스의 유일한 단점은 특정 값으로 불변 객체를 구분해야 한다는 점이다.
