💥 개요
불변클래스란 인스턴스 내부 값을 수정할 수 없는 클래스다. 불변 인스턴스에 간직된 정보는 생성된 순간부터 파괴되는 순간까지 절대 달라지지 않습니다. 자바에서 String, 기본 타입의 박싱된 클래스, BigInteger, BigDecimal이 여기에 속합니다.
📃 불변 클래스를 만드는 5가지 규칙
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다. (상속한 클래스에서 객체의 상태를 변하게 만드는 상태를 막아줌, 대표적인 방법으로 final Class)
- 모든 필드를 final로 선언한다.
- 모든 필드를 private으로 선언한다.
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
👍대표적인 불변 클래스
public final class Complex {
private final double re;
private final double im;
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);
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 dividedBy(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;
// See page 47 to find out why we use compare instead of ==
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)";
}
}
위와 같은 불변 객체는 단순합니다. 모든 메서드가 불변식을 보장하고, 스레드 안전하여 따로 동기화 할 필요도 없습니다. 안심하고 공유가 가능합니다. 이는 방어적 복사도 필요없게 됩니다.
불변 객체는 안전하게 공유가 가능하니 한번 만든 인스턴스를 재활용하는 방법이 좋습니다.(Point1)의 static 상수처럼요.
불변 클래스의 특징
- 불변 객체는 자유롭게 공유할 수 있고, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- 불변 객체는 그 자체로 실패 원자성을 제공한다. (메서드에서 예외가 발생해도 그 객체는 유효해야함)
- 불변 클래스의 단점으로는 값이 다르면 반드시 독립된 객체로 만들어야 해서 값의 가짓수가 많다면 큰 비용을 지불해야 한다.
불변클래스를 만드는 또 다른 설계방법
public class Complex {
private final double re;
private final double im;
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);
}
}
자신을 상속하지 못하게 하는 방법 중 가장 쉬운 방법은 final을 클래스에 붙이는 것 이지만, 더 유연한 방법으로 모든 생성자를 private 혹은 package-private으로 선언하고 정적 팩토리 메서드를 제공하는 것 입니다.
이 방법이 최선인 경우가 많습니다. 생성자가 private이니 이 클래스를 상속하는것은 불가능하고 정적 팩터리 방식으로 유연성을 지킬 수 있습니다.(다음 릴리스 때 객체 캐싱 기능을 추가할 수 있다.)
💡정리
1.getter가 있다고 꼭 setter를 만들면 안됩니다. 클래스는 꼭 필요한 경우가 아니면 불변이어야 합니다. Complex같은 단순한 값 객체는 불변이어야 하고 String과 BigInteger처럼 무거운 값 객체도 불변으로 만들 수 있는지 고려해야 합니다.
2.성능 때문에 어쩔 수 없다면 불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공해야 합니다.(String과 StringBuilder, StringBuffer)
3.불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분은 최소로 줄여야 합니다. 그래야 객체의 예측이 쉬워지고 오류가 생길 가능성이 줄어듭니다.(변경할 필드를 뺀 나머지는 final)
4.합당한 이유가 없다면 모든 필드는 private final이어야 합니다.
5.생성자는 불변식 설정이 모두 완료 된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 합니다.
❤️ 스터디 질답 정리
Q. "객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다." 에서 이점이 많다고 했는데 어떤 이점이 있는지?
A. 함수형 프로그램에서도 이점이 있는데 쉽게 사용할 수 있다.
Q. new 생성자로 계속 불변성을 지키려면 성능이슈가 있을것같은데 ?
A. 예전에는 문제가 됐지만, 요즘에는 상관이 거의 없다. 있는 경우는 클래스가 큰 경우일 것 같고, 이런 경우도 어쩔 수 없는 경우는 제외하고 클래스를 나눠야하는게 맞다. 아니라면 크게 상관이 없고 오히려 불변성을 지키는게 객체지향적이고 가독성이 높아진다.
'Study > 이펙티브 자바' 카테고리의 다른 글
[Effective Java 3E] 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) | 2023.09.16 |
---|---|
[Effective Java 3E] 상속보다는 컴포지션을 사용하라 (0) | 2023.09.16 |
[Effective Java 3E] public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.09.09 |
[Effective Java 3E] 클래스와 멤버의 접근 권한을 최소화하라 (2) | 2023.09.09 |