[Effective Java] 아이템17 - 변경 가능성을 최소화하라
불변 클래스란 그 인스턴스의 내부 값을 수정할 수 없는 클래스다. 불변 클래스는 가변 클래스보다 오류가 생길 여지가 적고 훨씬 안전하다.
- ex) String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal
불변 클래스 규칙
- 객체의 상태를 변경하는 메서드(setter)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 모든 필드를 final로 선언한다.
- 모든 필드를 private으로 선언한다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. (방어적 복사를 수행하라)
1. 객체의 상태를 변경하는 메서드를 제공하지 않는다.
필드를 final
, private
로 선언, 객체의 값을 변경하지 못하도록 한다.
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) { // 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다.
return new Complex(re + c.re, im + c.im);
}
public Complex minus(Complex c) {
return new Complex(re - c.re, im - c.im);
}
}
plus
와 minus
를 보면, 사칙연산을 수행하는 행동이 이 인스턴스 자체의 값을 수정하지는 않는다. Complex인스턴스를 새로 만들어 반환하는 것을 볼 수 있다.
이처럼 불변 객체는 생성된 시점의 상태를 파괴될 때까지 그대로 간직한다. 따라서 안심하고 공유할 수 있다.
1. 자주 쓰이는 값들은 상수(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);
2. 정적 팩터리를 제공해서 GC 비용을 줄인다.
불변 클래스의 단점
값이 다르면 반드시 독립된 객체로 만들어야 한다.
값의 가짓수가 많다면, 그 모두 독립된 객체로 만들어야하기 때문에 큰 비용을 처리해야 한다.
BigInteger moby = new BigInteger("100");
moby = moby.flipBit(0);
BitSet bitSet = new BitSet(100);
bitSet.flip(0);
BigInteger
는 원본과 단지 한 비트만 다른데 새로운 BigInteger를 생성한다.BitSet
은 가변이기 때문에, 원하는 비트 하나만 상수 시간안에 바꿔준다.
가변 동반 클래스 (companion class)
multisep operation (다단계 연산) 을 제공한다.
String
의 경우 StringBuilder
가 가변 동반 클래스이다. 이 클래스를 우리는 아래와 같이 사용한다.
StringBuilder sb = new StringBuilder();
sb.append("첫 번째");
sb.append("두 번째");
String result = sb.toString();
System.out.print(result);
이렇게 불변클래스인 String
을 계속 생성해서 추가하는게 아니라, StringBuilder
의 도움을 받아서 효과적으로 문자열을 조작하는 것이다.
이와 비슷하게, BigInteger
도 가변 동반 클래스가 있다. 하지만, package-private
이므로 클라이언트가 접근할 수 없게 되어 있다.
BitSieve
, MutableBigInteger
, SignedMutableBigInteger
와 같은 가변동반 클래스는 직접 사용하기에 더 어렵기 때문에 BigInteger
클래스 내부에서 사용하고 있다.
Q : 이럴거면 왜 BigInteger
내부에서 private 메소드로 두지 않았을까? </br>
A : 저 들은 가변 클래스다. 가변 클래스를 불변 클래스 안에 두면 안된다는 것을 상기시키면 어느정도 이해가 간다.
2. 자신을 상속하지 못하게 한다.
- final 클래스로 선언한다.
- 모든 생성자를
private
혹은package-private
으로 만들고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);
}
Summary
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 성능 때문에 어쩔 수 없다면 (다단계 연산이 필요할 경우) 불변 클래스와 쌍을 이루는 가변 동반 클래스를 제공하도록 하자.
- 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이자.
- 모두
private final
로 만들자
- 모두
댓글남기기