해당 포스트는 "Effective Java" 책의 내용을 요약한 것이다.
※ 변경 불가능 클래스
: 변경 불가능 클래스는 객체를 수정할 수 없는 클래스이다. 객체를 생성할 때 내부 정보가 다 채워지며 객체가 살아있는 동안 그 값이 보존된다. 변경 불가능 클래스를 사용하는 이유는 설계,구현,사용면에서 쉽다. 안전하고 오류 가능성도 적다.
- 변경 불가능 클래스 작성 규칙
1. 객체 필드를 변경하는 수정자 메서드를 제공하지 않는다.
2. 상속이 불가능하게 final로 선언한다. 상속을 하게 되면 악의적으로 필드 값을 변경하는 것처럼 보이게 할 수 있다.
3. 모든 필드는 final로 선언해 변경을 막는다. 그러면 동기화 없이 스레드들의 접근에 안전한다
4. 모든 필드는 private로 선언한다. 클라이언트가 필드에 참조에 접근하는 일을 막는다.
5. 변경 불가능 클래스에 포함된 변경 가능한 객체에 대한 참조를 외부(클라이언트)에서 불가능하게 해야 한다. 그런 필드는 클라이언트가 제공하는 객체로 초기화해서는 안 되고, 접근자가 그런 필드를 반환해서는 안 된다. 따라서 생성자나 접근자, readObject 메서드 안에서는 방어적 복사본을 만들어야 한다.
아래 코드는 위 사항에 맞추어 변경 불가능 클래스를 작성한 예이다.
public final class Complex { //private final로 작성했다. 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 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 (o == this) return true; if (!(o instanceof Complex)) return false; Complex c = (Complex) o; return Double.compare(re, c.re) == 0 && Double.compare(im, c.im) == 0; } @Override public int hashCode() { int result = 17 + hashDouble(re); result = 31 * result + hashDouble(im); return result; } private int hashDouble(double val) { long longBits = Double.doubleToLongBits(re); return (int) (longBits ^ (longBits >>> 32)); } @Override public String toString() { return "(" + re + " + " + im + "i)"; } }
변경 불가능 클래스 작성 규칙에서 클래스에 final을 선언하라고 했는 데 더 유연하게 작성할 수 있는 방법이 있다. 모든 생성자를 private나 package-private로 선언하고 public 생성자 대신 public 정적 팩터리를 제공하는 것이다. 해당 방법으로 위 예제를 바꿔보겠다.
private Complex(double re, double im) { this.re = re; this.im = im; } public static Complex valueOf(double re, double im){ return new Complex(re, im); }
위와 같이 사용하면 정적 팩터리의 장점들을 가질 수 있다. 또한 패키지 외부 입장에서는 public이나 protected로 선언된 생성자가 없기 때문에 final로 선언한 거나 마찬가지가 된다.
변경 불가능 클래스에서 모든 필드를 final로 선언할 필요는 없다. 성능 향상을 위해서 규칙을 완화할 수도 있다. 예를 들어 특정 변경 불가능 클래스는 시간이 많이 걸리는 계산에 대해서 비final 필드에 캐시해 둔다. 하지만 주의할 게 만약 변경 불가능 클래스에서 변경 가능 객체를 참조하는 필드가 있다면 직렬화를 구현할 때 readObject 나 readResolve 메서드를 반드시 제공해야 한다. 아니면 ObjectOutputSteam, writeUnshared나 ObjectInputStream.readUnshared 메서드를 반드시 사용해야 한다.
모든 get 메서드마다 set 메서드를 만들 필요는 없다. 변경 가능 클래스로 만들어야할 이유가 없다면 변경 불가능 클래스로 만들어라. 만약 변경 가능 클래스로 만들어야 한다면 변경 가능성을 최대한 줄이고 특별한 이유가 없다면 모든 필드에 대해서 final로 선언해라.
'자바 > 자바 성능' 카테고리의 다른 글
함수 객체 (0) | 2017.07.07 |
---|---|
상속(extends)와 구성(composition), 인터페이스 (0) | 2017.07.07 |
자바의 접근 권한과 효율적인 사용 방법 (0) | 2017.07.07 |
equals를 어떻게 재정의해야 하는가 (0) | 2017.07.06 |
불필요한 객체 생성X /유효기간이 지난 객체 폐기 (0) | 2017.07.06 |