티스토리 뷰
불변클래스
- 간단히 말해 그 인스턴스 내부 값을 수정할 수 없는 클래스이다.
- 불변 인스턴스의 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다.
- 대표적인 불변 클래스 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 |
링크
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- HTTP 완벽 가이드
- REST API
- 이펙티브자바
- 김영한 http
- 이펙티브자바 스터디
- 드림코딩
- 킹수빈닷컴
- js array
- BOJ
- GCP
- ㅇㄷㅇㅈ
- 패스트캠퍼스 컴퓨터공학 완주반
- Spring Security
- HTTP 완벽가이드
- js api
- 모던자바스크립트
- dreamcoding
- http
- js promise
- 이펙티브자바 아이템59
- 가상 면접 사례로 배우는 대규모 시스템 설계 기초
- 이펙티브자바 아이템60
- java
- 백준
- 백기선 스터디
- JPA 연관관계 매핑
- JS 딥다이브
- 프로그래머스 SQL
- 김영한 JPA
- 프로그래머스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
글 보관함