변경 가능성을 최소화하라
*불변 클래스: 인스턴스의 내부 값을 수정할 수 없는 클래스
자바 플랫폼에는 예를 들어 String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal가 있다.
불변 클래스로 설계하는 이유는 (1) 가변보다 사용하기 쉬우며, (2) 오류가 생길 여지가 적고 안전하다.
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 Complex plus(Complex c) {
return new Complex(re + c.re, im + c.im); //새로운 객체 반환
}
}
위의 코드를 보면, 인스턴스 자신은 수정하지 않고 새로운 인스턴스를 만들어 반환한다. (함수형 프로그래밍 패턴이라 한다.) 즉, 생성된 시점의 상태를 파괴될 때까지 그대로 간직하고 있다.
근본적으로 스레드 안전하여 따로 동기화할 필요가 없다.
여러 스레드가 동시에 사용해도 훼손되지 않는다. 불변 객체에 대해서는 그 어떤 스레드도 다른 스레드에 영향을 줄 수 없으니 불변 객체는 안심하고 공유할 수 있다.
가장 쉬운 재활용 방법으로는 자주 쓰이는 값을 상수 public static final
로 제공하는 것이다.
//상수 예시
pulbic static final complex ZERO = new complex(0, 0);
불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
예를 들어, BigInteger 클래스는 부호를 int변수
, 크기를 ìnt배열
을 사용한다. 여기서 negate 메서드는 크기는 같고 부호만 반대인 새로운 BigInteger를 생성하는데, 크기는 복사하지 않고 원본 인스턴스와 공유해도 된다.
객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
값이 바뀌지 않는 구성요소들로 이뤄진 객체라면 불변식을 유지하기에 수월하다. 예를 들어 불변 객체는 맵의 키와 Set의 원소로 쓰기에 좋다.
불변 객체는 그 자체로 실패 원자성
을 제공한다.
failure atomicity: 메서드에서 예외가 발생한 후에도 객체 상태는 메서드 호출 전과 같아야 한다.
불변 클래스는 값이 다르면 반드시 독립된 객체로 만들어야 한다.
비트 하나라도 다르다면 비트 하나만 다른 새로운 인스턴스를 생성하도록 한다.
대처하는 방법 중 하나는 다단계 연산들을 예측하여 기본 기능으로 제공하는 방법이다. 다단계 연산을 기본으로 제공한다면 더 이상 각 단계마다 객체를 생성하지 않아도 된다. (BigInteger는 모듈러 지수 같은 다단계 연산 속도를 높여주는 가변 동반 클래스를 Package-private으로 두고 있다.) 복잡한 연산을 예측하기 어렵다면 public 으로 제공한다.
자신을 상속하지 못하게 해야 한다.
즉, 모든 생성자를 private
혹은 package-private
으로 만들고 public 정적 팩터리를 제공하는 방법이다.
public final class Complex {
private final double re;
private final double im;
private Complex(double re, double im) { //생성자는 private
this.re = re;
this.im = im;
}
public static Complex valueOf(double re, double im){ //정적 팩터리
return new Complex(re, im);
}
}
BigInteger과 BigDecimal는 재정의할 수 있도록 설계가 되어 있다. 이 경우, 신뢰할 수 없는 하위 클래스의 인스턴스라고 확인된다면 방어적으로 복사해 사용해야 한다.
public static BigInteger safeInstane(BigInteger val) { //클래스 확인
return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray());
}