-
[Effective Java] 아이템17: 변경 가능성을 최소화하라Language/Java 2022. 6. 29. 16:00
✔️ 자바 플랫폼 라이브러리의 불변 클래스
- String
- 기본 타입의 박싱된 클래스들
ex) Integer, Long, Double - BigInteger
- BigDecimal→
✔️ 불변 클래스 만들기 위한 규칙 (5가지)
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
→ 2가지 방법이 있다. (클래스 상속 방지하는 방법 참고) - 모든 필드를
final
로 선언한다. - 모든 필드를
private
으로 선언한다. - 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행하라.
✔️ 불변 클래스 예시: 복소수 클래스
// 불변 복소수 클래스 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) { 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; // == 대신 compare를 사용하는 이유는 63쪽을 확인하라. 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)"; } }
📌 final 클래스
- 확장이 불가능하도록 Complex 클래스를 final로 선언하였다.
📌 re, im 필드
- 모든 필드는 final, private으로 선언한다.
📌 사칙연산 메서드 (plus, minus, times, dividedBy)
- 인스턴스 자신은 수정하지 않고 새로운 Complex 인스턴스를 만들어 반환한다.
- 객체의 값을 변경하지 않는다는 사실을 강조하기 위해, 메서드 이름으로 동사(add) 대신 전치사(plus)로 사용했다.
함수형 프로그래밍
: 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴.
✔️ 불변 객체 장점
- 근본적으로 스레드 안전(thread-safe)하여 동기화할 필요가 없다.
다른 스레드에 영향을 끼치지 않으므로 안심하고 공유할 수 있다.
방어적 복사도 필요 없다. 따라서, clone 메서드나 복사 생성자를 제공하지 않는 게 좋다. - 불변 객체끼리는 내부 데이터를 공유할 수 있다.
- 객체를 만들 때 불변 객체들을 구성요소로 사용하면, 불변식을 유지하기 훨씬 수월하다.
ex) Map의 key와 Set의 원소로 불변 객체를 사용하는 것. - 불변 객체는 그 자체로
실패 원자성
(failure atomicity)를 제공한다.
*실패 원자성: 메서드에서 예외가 발생한 후에도 그 객체는 여전히 유효한 상태여야 한다는 성질.
✔️ 불변 객체 단점
- 값이 다르면 반드시 독립된 객체로 만들어야 한다.
- 값의 가짓수가 많다면 이들을 모두 만드는 데 큰 비용을 치러야 한다.
📌 해결 방법 (2가지)
- 다단계 연산(multistep operation)을 정확히 예측할 수 있다면, package-private
가변 동반 클래스
(companion class)를 둔다.
ex) StringBuilder - 다단계 연산을 예측할 수 없다면, 클래스를
public
으로 제공한다.
✔️ 클래스 상속 방지하는 방법
클래스를 상속하지 못하게 하는 방법은 2가지 있다.
- final 클래스로 선언한다. (위의 예제 코드에서 사용한 방법)
- 모든 생성자를 private 혹은 package-private으로 만들고, public 정적 팩터리를 제공한다.
두 번째 방법을 사용한 예제 코드
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); } ... }
- 생성자를 private으로 제공한다.
- public 정적 팩터리인 valueOf 메서드를 제공한다.
핵심 정리
- getter가 있다고 해서 무조건 setter를 만들지는 말자.
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 단순한 값 객체는 항상 불변으로 만들자.
무거운 값 객체도 불변으로 만들 수 있는지 고심하여야 한다. - 성능 때문에 불변으로 만들 수 없다면, 불변 클래스와 쌍을 이루는
가변 동반 클래스
를public
클래스로 제공하도록 하자. - 불변으로 만들 수 없는 클래스는 변경할 수 있는 부분을 최소한으로 줄이자.
다른 합당한 이유가 없다면 모든 필드는private final
이어야 한다. - 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
확실한 이유가 없다면 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안 된다.
728x90'Language > Java' 카테고리의 다른 글
[Effective Java] 아이템19: 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) 2022.07.09 [Effective Java] 아이템18: 상속보다는 컴포지션을 사용하라 (0) 2022.07.02 [Effective Java] 아이템16: public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) 2022.06.24 [Effective Java] 아이템15: 클래스와 멤버의 접근 권한을 최소화하라 (0) 2022.06.22 [Effective Java] 아이템14:Comparable을 구현할지 고려하라 (0) 2022.06.20