인스턴스의 내부 값을 수정할 수 없는 클래스로 객체가 파괴되는 순간까지 절대 값이 달라지지 않는다. => 단순하다.
가변 클래스보다 설계, 구현, 사용이 쉬우며 오류가 생길 여지도 적고 안전하다.
String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal ...
객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
클래스를 확장할 수 없도록한다.
=> 하위 클래스에서 객체의 상태를 변하게 만드는 사태를 막아준다. 상속을 막는 방법으로 클래스를 final로 선언한다.
모든 필드를 final로 선언한다.
=> 시스템이 강제하는 수단을 이용해 설계자의 의도를 명확히 드러내는 방법
모든 필드를 private으로 선언한다.
=> 필드가 참조하는 가변 객체를 클라이언트에서 직접 접근해 수정하는 일을 막아준다.
=> public final로 선언해도 불변 객체가 되지만, 이렇게 하면 다음 릴리스에서 내부 표현을 바꾸지 못하므로 권하지는 않는다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
=> 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야햔다.
=> 접근자 메서드가 그 필드를 그대로 반환해서도 안 된다. 생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행하라.
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); // 새로운 인스턴스를 만들어서 반환
}
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 divdedBy(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;
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)";
}
}
모든 생성자가 불변식을 보장한다면 프로그래머가 노력을 들이지 않아도 불변으로 남는다.
=> 가변객체의 경우 상태 전이를 정밀하게 문서로 남겨놓지 않으면 믿고 사용하기 어렵다.
불변 객체는 근본적으로 스레드 안전하여 따로 동기화할 필요 없다.
=> 여러 스레드가 동시에 사용해도 절대 훼손 x, 안심하고 공유 가능 !
따라서 한 번 만든 인스턴스를 최대한 재활용하기를 권한다.
=> 가장 쉬운 방법은 인스턴스를 상수로 제공하는 것 -> 정적 팩터리 사용하기
방어적 복사가 필요없다. 아무리 복사해봐야 원본과 같으므로 복사 자체가 의미가 없다.
=> clone 메서드나 복사 생성자를 제공하지 않는게 좋다.
불변 객체끼리는 내부 데이터를 공유할 수 있다.
=> BigInteger 클래스는 내부에서 값의 부호와 크기를 따로 표현한다. 부호는 int , 크기에는 int 배열을 사용한다.
=> negate메서드는 크기가 같고 부호만 반대인 새로운 BigInteger를 생성하는데 배열은 가변이지만 복사하지 않고 원본 인스턴스와 공유해도된다. 새로운 BigInteger 인스턴스도 원본 인스턴스가 가리키는 내부 배열을 그대로 가리킨다.
map의 키와 set의 원소로 쓰기에 좋다. 불변식이 허물어질 걱정을 하지 않아도 된다.
불변 객체는 그 자체로 실패 원자성을 제공한다. 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
=> 실패 원자성 ? 메서드에서 예외가 발생한 후에도 그 객체는 메서드 호출 전과 똑같은 유효한 상태여야 한다는 성질
final 클래스로 선언하기
모든 생성자를 private 혹은 package-private로 만들고 public 정적 팩터리를 제공하는 방법
=> 다른 패키지에서 이 클래스를 확장하는 것이 불가능 하다.
=> 정적 팩터리 방식은 유연성을 제공하고 다음 릴리스에서 객체 캐싱 기능을 추가해 성능을 끌어올릴 수도 있다.