본문으로 바로가기
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.



변경 불가능 클래스는 그 객체를 수정할 수 없는 클래스다. 객체 내부의 정보는 객체가 생성될 때 주어진 것이며, 객체가 살아 있는 동안 그대로 보존된다. 자바 플랫폼 라이브러리에는 이런 클래스가 많다. String, 기본 자료형 클래스, Biginteger, BigDecimal 등이 그런 클래스다. 변경 불가능 클래스를 만드는 이유는 다양하다. 우선 변경 불가능 클래스는 변경 가능 클래스보다 설계하기 쉽고, 구현하기 쉽고, 오류 가능성이 적고 더 안전하다.


변경 불가능 클래스를 만들 때 5가지 아래 규칙


1. 객체 생태를 변경하는 메서드(수정자 및 setter)를 제공하지 않는다.

2. 계승할 수 없도록 한다. 잘못 작성되거나 악의적인 하위 클래스가 객체 상태가 변경된 것처럼 동작해서 변경 불가능성을 깨뜨리는 일을 막을 수 있다. 계승을 막으려면 클래스를 final로 선언하면 된다.

3. 모든 필드를 final 필드로 선언한다.

4. 모든 필드를 private 로 선언한다. 그러면 클라이언트가 필드를 참조하는 변경 가능 객체를 직접 수정하는 일을 막을 수 있다. 

5. 변경 가능 컴포넌트에 대한 독점적 접근권을 보장한다. 클래스에 포함된 변경 가능 객체에 대한 참조를 클라이언트는  획득할 수 없어야 한다. 그런 필드는 클라이언트가제공하는  객체로 초기화 해서는 안되고, 접근자 또한 그런 필드를 반환해서는 안된다. 따라서 생성자나 접근자, readObject 메서드 안에서는 방어적 복사본을 만들어야한다.


아래 예제는 복소수를 표현하는 클래스로, 실수부와 허수부를 가져오기 위한 접근자, 사칙연산에 대응되는 메서드를 제공한다. 사칙연산은 새로운 Complex 객체를 만들어 반환한다. 대부분의 변경 불가능 클래스가 따르는 패턴이다. 함수형 접근법으로도 알려져 있는데 연산을 적용한 결과를 새롭게 만들어 반환하기 때문이다. 


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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)";
    }
}



변경 불가능 클래스의 장점


변경 불가능 객체는 단순하다. 변경 불가능 객체는 스레드에 안전할 수밖에 없다. 어떤 동기화도 필요 없으며, 여러 스레드가 동시에 사용해도 상태가훼손될 일이 없다. 스레드 안전성을 보장하는가장 쉬운 방법이다. 사실 스레드는 다른 스레드가 변경 불가능한 객체에 무슨 짓을 하는지 알 수 없다. 따라서 변경 불가능한 객체는 자유롭게 공유할 수 있다. 


변경 불가능 클래스는 클라이언트가 기존 객체를 재사용하도록 적극 장려해서 이런 장점을 충분히 살릴 필요가 있다. 그렇게 하는 쉬운 방법은 public static final 상수를 만들어 제공하는 것이다.


이 접근법을 개선하여 자주 변경 불가능 클래스는 자주 사용하는 객체를 캐시하여 이미 있는 객체가 거듭 생성되지 않도록 하는 정적 팩터리를 제공한다. 기본 자료형에 대한 객체 클래스들과 BigInteger 클래스는 그렇게 구현되어 있다. 이런 정적 팩터리를 메서드를 사용하면 클라이언트는 새로운 객체를 만드는 대신 기존 객체들을 공유하게 되므로 메모리 요구량과 쓰레기 수집 비용이 줄어든다. 새로운 클래스를 설계할 때 public 생성자 대신 정적 팩터리 메서드를 제공하면 나중에 클라이언트 코드를 변경하지 않고도 캐시 기능을 추가할 수 있다.


변경 불가능한 객체를 자유롭게 공유할 수 있다는 것은, 방어적 복사본을 만들 필요가 없다는 것이다. 따라서 변경 불가능한 클래스에 clone 메서드나 복사 생성자를 만들어서도 안된다.


변경 불가능한 객체는 그 내부도 공유할 수 있다, 변경 불가능한 객체는 다른 객체의 구성 요소로도 훌륭하다.


변경 불가능 객체의 유일한 단점은 값마다 별도의 객체를 만들어야 한다는 점이다. 따라서 객체 생성 비용이 높을 가능성이 있다. 큰 객체라면 특히 더 그렇다.


모든 생성자를 private나 package-private로 선언하고 public 생성자 대신 public 정적 팩터리를 사용하면 좀 더 유연한 변경 불가능한 클래스를 만들 수 있다.


1
2
3
4
5
6
7
8
private Complex(double re, double im) {
    this.re = re;
    this.im = im;
}
 
public static Complex valoeOf(double re, double im) {
    return new Complex(re, im);
}



요약하자면 변경 가능한 클래스를 만들 타당한 이유가 없다면, 반드시 변경 불가능 클래스로 만들어야 한다. 

변경 불가능한 클래스를 만들 수 없다면, 변경 가능성을 최대한 제한하라. 

특별한 이유가 없다면 모든 필드를 final로 선언하라.