본문은 Effective Java를 읽고 정리한 내용을 기반으로 작성된 글입니다.
불변 클래스란 간단히 말해 그 인스턴스의 내부 값을 수정할 수 없는 클래스다. 불변 인스턴스에 간직된 정보는 고정되어 객체가 파괴되는 순간까지 절대 달라지지 않는다. 불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기 쉬우며, 오류가 생길 여지도 적고 훨씬 안전하다.
☑️ 불변 클래스 생성 규칙
- 객체의 상태를 변경하는 메서드(변경자)를 제공하지 않는다.
- 클래스를 확장할 수 없도록 한다.
- 상속을 막는 대표적인 방법 ↔ 클래스를 final로 선언하기
- 모든 필드를 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 Complex plus(Complex c){
return new Complex(re + c.re, im + c.im);
}
}
☑️ 함수형 프로그래밍
: 피연산자에 함수를 적용해 그 결과를 반환하지만, 피연산자 자체는 그대로인 프로그래밍 패턴
↔ 사칙연산 메서드 plus가 인스턴스 자신은 수정하지 않고, 새로운 Complex 인스턴스를 만들어 반환함
☑️ 메서드 이름으로 전치사 사용
: 메서드 이름으로 (add 같은) 동사 대신 (plus 같은) 전치사를 사용함
↔ 해당 메서드가 객체의 값을 변경하지 않는다는 사실을 강조하려는 의도
[불변 클래스의 장단점]
☑️ 장점
- 불변 객체는 단순하다.
- 생성된 시점의 상태를 파괴될 때까지 그대로 간직한다.
- 불변 객체는 스레드 안전하여 따로 동기화할 필요 없다.
- 여러 스레드가 동시에 사용해도 절대 훼손되지 않는다.
- 불변 객체는 안심하고 공유할 수 있다.
- 따라서 자주 쓰이는 값들을 최대한 재활용하기를 권한다 ↔ 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리 제공 가능
- 방어적 복사가 필요 없다.
- 불변 객체는 자유롭게 공유할 수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유할 수 있다.
- 객체를 만들 때 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
- 불변 객체는 그 자체로 실패 원자성(메서드에서 예외가 발생한 후에도 그 객체는 여전히 메서드 호출 전과 똑같은 유효한 상태여야 한다는 성질)을 제공한다.
☑️ 단점
- 값이 다르면 반드시 독립된 객체로 만들어야 한다 → 값의 가짓수가 많다면 이들을 모두 만드는 데 큰 비용을 치러야 한다.
이러한 성능 문제에 대한 해결 방법
- 흔히 쓰일 다단게 연산들을 예측하여 기본 기능으로 제공하기
- ex) BigInteger 클래스의 연산 속도를 높이기 위한 가변 동반 클래스
- 연산을 예측하기 어렵다면, 해당 클래스를 public으로 제공하기
- ex) String 클래스의 가변 동반 클래스 StringBuilder
[불변 클래스 설계 방법]
☑️ 클래스가 불변임을 보장하려면?
: 자신을 상속하지 못하게 해야 함
- 가장 쉬운 방법은 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;
}
private static Complex valueOf(double re, double im){
return new Complex(re, im);
}
}
public이나 protected 생성자가 없음 → 다른 패키지에서는 이 클래스를 확장할 수 없음 → 패키지 바깥에서 바라본 이 불변 객체는 사실상 final이 된다.
☑️ 불변 클래스의 규칙에 따르면, 모든 필드가 final이고 어떤 메서드도 그 객체를 수정할 수 없어야 한다?
성능을 위해 완화 가능 → 어떤 메서드도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다.
- 계산 비용이 큰 값을 나중에 계산하여, final이 아닌 필드에 캐시해놓음 → 비용 절감 가능
[클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다]
- getter가 있다고 해서 무조건 setter를 만들지는 말자(ㅎ)
- 불변으로 만들 수 없는 클래스라도 변경 가능한 부분을 최소한으로 줄이자.
- 객체가 가질 수 있는 상태의 수를 줄이면 → 예측 가능, 오류 줄어듦 → 그러니 꼭 변경해야 할 필드를 뺀 나머지 모두를 final로 선언하자
- 다른 합당한 이유가 없더라면 모든 필드는 private final이어야 한다.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.
- 확실한 이유가 없다면, 생성자와 정적 팩터리 외에는 그 어떤 초기화 메서드도 public으로 제공해서는 안 된다.
'Book > 이펙티브 자바' 카테고리의 다른 글
[Effective Java] item 18. 상속보다는 컴포지션을 사용하라 * (1) | 2023.10.06 |
---|---|
[Effective Java] item 16. public 클래스에서는 public 필드가 아닌 접근자 메서드를 사용하라 (0) | 2023.09.29 |
[Effective Java] item 15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2023.09.24 |