-
아이템17. 변경 가능성을 최소화하라책/이펙티브자바 2021. 7. 27. 12:55
불변클래스
- 간단히 말해 그 인스턴스 내부 값을 수정할 수 없는 클래스이다.
- 불변 인스턴스의 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다.
- 대표적인 불변 클래스 ex. String, Wrapper class, BigInteger, BigDecimal
불변클래스의 장점
- 가변 클래스보다 설계하고 구현하고 사용하기 쉽다.
- 오류가 생길 여지가 적다.
- 안전하다.
- 생성된 시점의 상태를 파괴될 때까지 그대로 간직한다.
- 근본적으로 스레드 안전하여 따로 동기화 할 필요가 없다.
- 안심하고 공유할 수 있다.
불변클래스를 만드는 법
- 객체의 상태를 변경하는 메소드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 하위클래스에서 객체의 상태를 변경하는 사태를 막아준다.
- 대표적인 방법은 클래스를 final 로 선언하는 것이다.
- 모든 필드를 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.re + 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)); } // equals, hashCode, toString .. }
- 사칙연산 메소드들이 인스턴스 자신을 수정하는게 아니라 새로운 Complex 인스턴스를 만들어 반환한다.
- 이처럼 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴을 함수형 프로그래밍이라 한다.
- 메소드 이름으로 동사형
add
대신 전치사plus
를 사용했다. 이는 해당 메소드가 객체의 값을 변경한게 아니라는 사실을 강조하려는 의도이다.
불변클래스 재활용
- 불변 클래스는 한 번 만든 인스턴스를 최대한 재활용하기를 권한다.
- 가장 쉬운 재활용 방법은 자주 쓰이는 값들을 상수(public static final)로 제공하는것이다.
- ex. 박싱된 기본 타입 클래스, BigInteger
- 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩토리를 제공할 수 있다.
- 이런 정적 팩토리를 사용하면 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.
- 새로운 클래스를 설계할 때 public 생성자 대신 정적 팩토리를 만들어두면, 클라이언트를 수정하지 않고도 필요에 따라 캐시 기능을 나중에 덧붙일 수 있다.
- 불변 객체를 자유롭게 공유할 수 있다는 점은 방어적 복사도 필요 없다는 결론으로 이어진다.
- 그러니 불변 클래스는 clone 메소드나 복사 생성자를 제공하지 않는게 좋다.
// Complex.class 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);
성질
객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- 값이 바뀌지 않는 구성요소들로 이루어진 객체라면 불변식을 유지하기 훨씬 수월하기 때문이다.
- ex. 불변 객체는 Map의 key와 Set의 원소로 쓰기에 안성맞춤이다. Map이나 Set은 안에 담긴 값이 바뀌면 불변식도 허물어지는데, 불변 객체 사용시 그런 걱정이 필요가 없다.
불변 객체는 그 자체로 실패 원자성을 제공한다.
- 상태가 절대 변하지 않으니 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
- 원자성 (atomicity): 메소드에서 예외가 발생한 후에도 그 객체는 여전히 호출전과 유효한 상태 상태여야 한다.
"모든 필드가 final 이고 어떤 메서드도 그 객체를 수정할 수 없어야 한다"
→ "어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다"
불변 클래스의 단점
- 값이 다르면 반드시 독립된 객체로 만들어야 한다.
- 값의 가짓수가 많다면 이들을 모두 만드는 데 큰 비용을 치러야 한다.
- 원하는 객체를 완성하기까지의 단계가 많고, 그 중간 단계에서 만들어서 버려진다면 성능 문제가 더 불거진다.
불변클래스의 성능 대처 방법
- 다단계 연산 (multistep operation)
- 다단계 연산을 기본으로 제공한다면 각 단계마다 객체를 생성하지 않아도 된다.
- ex. String → StringBuilder 의 append()
불변클래스를 만드는 법
- final class 만들기
- 모든 생성자를 private 혹은 package-private으로 만들고 public 정적 팩토리메소드 제공
// 예시 2 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); } // ... }
BigInteger, BigDecimal
- 이전 설계당시의 문제점으로 인해 불변이라는 보장이 없다.
- 만약 신뢰할 수 없는 클라이언트로부터 BigInteger나 BigDecimal의 인스턴스를 받는다면 주의해야 한다.
- 신뢰할 수 없는 하위 클래스의 인스턴스라고 확인되면, 가변이라 가정하고 방어적으로 복사해 사용해야 한다.
// 방어적 복사의 예시 public static BigInteger safeInstance(BigInteger val) { return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray()); }
결론
- getter가 있다고 무조건 setter를 만들지는 말자.
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 불변으로 만들 수 없더라도 변경할 수 있는 부분을 최소한으로 줄이자.
- 꼭 변경해야 할 필드를 제외한 나머지 모두를 final로 선언하자.
- 다른 합당한 이유가 없다면 모든 필드는 private final이어야 한다.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
- 확실한 이유가 없다면 생성자와 정적팩토리 외의 초기화 메서드는 public으로 제공해서는 안된다.
'책 > 이펙티브자바' 카테고리의 다른 글
아이템19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라 (0) 2021.08.17 아이템18. 상속보다는 컴포지션을 사용하라 (0) 2021.08.15 아이템16. public 클래스에는 public 필드가 아닌 접근자 메서드를 사용하라 (0) 2021.07.26 아이템15. 클래스와 멤버의 접근 권한을 최소화하라 (0) 2021.07.26 아이템14. Comparable을 구현할지 고려하라 (0) 2021.07.25