Itme 17은 불변클래스에 대한 이야기 이다.
불변 클래스란?
불변 클래스란 간단히 말해 그 인스턴스 내부 값을 수정 할 수 없는 클래스이다. 불변 인스턴스에 간직된 정보들은 객체가 파괴되는 순간까지 달라리지 않는다. String
, BigInteger
, BigDeciamal
클래스가 불변 클래스에 속한다. 불변 클래스는 가변 클래스 보다 설계하고 구현하고 사용하기 쉽다. 또한 오류가 생길 여지도 적고 훨씬 안전하다.
클래스를 불변으로 만들기 위한 규칙
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- Setter와 같은 메소드를 만들지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 클래스를 상속하면 메서드를 오버라이딩 하거나 추가하면 변경이 가능하도록 만들 수 있다. final 클래스로 선언하여 클래스를 상속하지 못하게 해야한다.
- 모든 필드를 final로 선언한다.
- Java 언어 차원에서 지원하는 final 키워드를 사용하면 변경을 제한 할 수 있다. -> 필드에 대한 수정을 막겠다는 설계자의 의도를 보여준다.
- Java 에서는 final로 선언된 필드는 멀티스레드에도 안전하다.
- 모든 필드를 private으로 선언한다.
- 필드가 참조하는 가변 객체를 클라이언트에서 수정하는 일을 막아준다.
- 참조형이 아닌 기본 타입 필드 는 public fianl로 만 선언해도 불변 객체가 되지만 아이템 15, 16를 참고하면 public으로 필드를 선언하는것은 권하지 않는다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 클라이언트에서 인스턴스 내에 가변 객체의 참조를 없게 해야한다. Collection 같은 가변 객체 필드에 대한 Getter가 없어야한다.
- 생성자, 접근자, readObject 메소드 모두에게 방어적 복사를 수행해야한다.(아이템 88)
불변 클래스를 만들기 위한 규칙을 지켜 만든 Complex
클래스이다.
public final class Complex {
private final double re;
private final double im;
public Complex(double re, double im) {
this.re = re;
this.im = im;
}
public double realPart() { return re; }
public double imaginaryPart() { return im; }
public Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im);
}
// 코드 17-2 정적 팩터리(private 생성자와 함께 사용해야 한다.) (110-111쪽)
public static Complex valueOf(double re, double im) {
return new Complex(re, im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
public Complex times(Complex c) {
return new Complex(re * c.re - im * c.im,
re * c.im + im * c.re);
}
public Complex dividedBy(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);
}
@Override public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Complex))
return false;
Complex c = (Complex) o;
// == 대신 compare를 사용하는 이유는 63쪽을 확인하라.
return Double.compare(c.re, re) == 0
&& Double.compare(c.im, im) == 0;
}
@Override public int hashCode() {
return 31 * Double.hashCode(re) + Double.hashCode(im);
}
@Override public String toString() {
return "(" + re + " + " + im + "i)";
}
}
위 클래스의 메소드들은 객체의 값을 변경하지 않고 새로운 Complex 인스턴스를 반환한다.
이와 같이 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍 이라고한다.
메서드의 이름으로 add 와 같은 동사 대신 plus 와 같은 전치사를 사용하는 명명규칙이 존재한다
이러한 명명규칙은 해당 메서드가 객체의 값을 변경하지 않는다는 사실을 강조하는 의도이다.
(참고 : BigInteger 클래스는 해당 명명 규칙을 지키지 않은 불변 클래스 이다.)
불변 클래스의 장점
- 불변 객체는 생성 시점 부터 소멸될 때 까지 변하지 않는다.
- 불변 객체는 근본적으로 스레드에 안전하여 따로 동기화할 필요 없다.
- 불변 객체는 안심하고 공유할 수 있다.
public static final Complex ONE = new Complex(1, 0);
public static final Complex I = new Complex(0, 1);
- 위와 같이 상수로 사용해 최대한 한번 만든 인스턴스를 재활용하기를 권한다.
- 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수잇다.
- 불변 객체는 자유롭게 공유할 수 있으며, 불변 객체끼리는 내부데이터를 공유 할 수 있다.BigInteger 클래스는 내부에서 부호와 크기를 따로 표현한다.
negate()
로 부호가 반대인 BigInteger 생성지 복사 과정 없이 공유한다
public class BigInteger extends Number implemtns Comparable {
final int signum; // 부호
final int[] mag; // 크기
public BigInteger negate() {
return new BigInteger(this.mag, -this.signum);
}
}
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다. (불변 객체를 key로 하면 이점이 많다.)
- Map이나 Set은 안에 담긴 값이 바뀌면 불변식이 허물어 지는데, 불변 객체를 사용하면 그런 걱정은 하지 않아도 된다.
- 불변 객체는 그자치로 실패 원자성을 제공한다. (아이템 76)
- 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
불변 클래스의 단점
- 값이 다르다면 반드시 독립된 새 인스턴스를 만들어 주어야 한다.
불변 클래스 단점 해결
- 가변 동반 클래스
- String 클래스의 StringBuilder 클래스
- BigInteger 클래스의 MutableBigInteger 클래스
- MutableBigInteger클래스는 BigInteger 클래스의 메서드들이 내부적으로 사용한다.
정리
- 클래스는 꼭 필요한 경우가 아니면 불변이어야 한다.
- 불변으로 만들 수 없는 클래스라도 가변부분을 최대한 줄이자.
- 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.