아이템 15. 변경 가능성을 최소화 하라.

15. 변경 가능성을 최소화 하라.

변경 불가능(Immustable)클래스의 장점은 설계 하기 쉽고, 사용하고 쉬으며, 오류 가능성도 적고, 멀티 스레드 환경에 안전하다. Java에서 다양한 변경 불가능 클래스들이 존재하는데, String, BigInteger, BigDecimal등 있다.

변경 불가능 클래스를 만들 때는아래의 다섯 규칙을 따르면 된다.

  1. 객체 상태를 변경하는 메서드(수정자(mutator)메서드 등)을 제공하지 않는다.
  2. 계승할 수 없도록 한다. final class로 선언한다.
  3. 모든 필드를 final로 선언한다. 이렇게 지정하면 자바 메모리 모델에 명시된 바와 같이(JLS, 17.5; Goetz06, 16) 새로 생성된 객체에 대한 참조가 동기화(synchronization)없이 다른 스레드로 전달되어도 안전하다.
  4. 모든 필드를 private으로 선언한다.
  5. 변경 가능 컴포넌트에 대한 독점적 접근권을 보장한다. 클래스에 포함된 변경 가능 객체에 대한 참조를 클라이언트는 획득할 수 없어야 한다.그런 필드는 클라이언트가 제공하는 객체로 초기화해서는 안되고, 접근자(accessor)또한 그런 필드를 반환해서는 안된다. 따라서 생성자나 접근자 readObject메서드안에는 방어적 복사본(defensive copy)를 만들어야 한다.
/**
 * 복소수(complex number, 실수(re)와 허수부(im)를 갖는 수)
 **/
public final class Complex{
	private final double re;
	private final double im;
	
	private Complex(double re, doube im){
		this.re = re;
		this.im = im;
	}
	
	public static Complex valueOf(double re, double im){
		return new Complex(re, im);
	}
	
	// 수정자(setter)가 존재 하지 않도록 구성
	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);
	}
	
	@Override
	public boolean equals(Object o){
		if( this == o ){
			return true;
		}
		if( false == (o instanceof Complex) ){
			return false;
		}
		
		Complex c = (Complex)o;
		return 0 == Double.compare(re, c.re) &&
			   0 == Double.compare(im, c.im);
		
	}
	
	@Override
	public int hashCode(){
		int result 17 + hashDouble(re);
		result = (31 * result) + hashDouble(im);
		return result;
	}
	
	private static int hashDouble(double val){
		long longBits = Double.doubleToLongBits(val);
		return (int) (longBits ^ (longBits >>> 32));
	}
	
	@Override
	public String toString(){
		return "(" + re + " + " + im + "i)";
	}
}

변경 불가능 객체는 단순하다. 생성될때 부여된 한가지 상태만 갖는다.

자주 사용되는 불변 객체는 아래와 같이 public static final상수로 만들어, 캐시하여 사용하자.

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);

복사 생성자는 만들지 말자 : 변경 불가능한 객체의 내부 데이터는 공유해도 안전하기 때문이다.
예를 들어, BigInteger 클래스는 값의 부호와 크기를 각각 int변수와 int 배열로 표현한다. negate 메서드는 같은 크기의 값을 부호만 바꿔서 새로운 BigInteger객체로 반환한다. 그러나, 내부 배열은 복사하지 않는다. 새로운 BigInteger객체도 원래 객체와 같은 내부 배열을 참조한다.

변경 불가능 객체는 다른 객체(map, set)의 구성 요소로도 훌룡하게 적용 가능하다.

변경 불가능 객체의 유일한 단점은 값마다 별도의 객체를 만들어야 한다는 점이다. 따라서, 객체의 생성 비용이 높을 가능성이 크다.

규칙 15 변경 가능성을 최소화하라. (107p~ 108p)를 다시 한번 꼭 읽어 볼것!!!