이펙티브 자바 - item 17. 변경 가능성을 최소화하라
on JAVA
이펙티브 자바 - item 17. 변경 가능성을 최소화하라
불변 클래스
- 불변 클래스란 그 인스턴스 내부 값을 수정할 수 없는 클래스
- 객체가 파괴되는 순간까지 절대 달라지지 않는다.
- ex. String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal ..
- 가변 클래스보다 설계하고 구현하고 사용하기 쉽고 오류가 생길 여지가 적으며 훨씬 안전하다.
- 멀티쓰레드 환경에서 유리하다.
불변 클래스를 만드는 다섯가지 규칙
- 객체의 상태를 변견하는 메서드(변경자) 즉, setter를 제공하지 않는다.
- 클래스를 확장 할 수 없도록 한다
- 하위 클래스에서 객체의 상태를 변하게 만드는 상태를 막아준다. ( 상속을 허용하지 않는다.)
- 상속을 막는 방법에는 final로 선언하는 방법과, 생성자를 제공하지 않고 정적 팩터리 메서드만 제공하는 방법이 있다.
- 모든 필드는 final로 선언한다.
- 시스템이 강제하는 수단을 이용해 설계자의 의도를 명확히 드러내는 방법
- 새로 생성된 인스턴스를 동기화 없이 다른 스레드로 건네도 문제없이 동작하게끔 보장하는데도 필요하다
- 모든 필드를 private으로 선언한다.
- 필드가 참조하는 가변 객체를 클라이언트에서 직접 수정하는 일을 막아준다.
- 기술적으로 필드를 public final로 선언해도 불변 객체가 되지만 내부 표현을 바꾸지 못하므로 권장 X
- 자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다.
- 클래스에 가변 객체를 참조하는 필드가 하나라도 있다면 클라이언트에서 그 객체의 참조를 얻을 수 없도록 해야한다.
- 접근자 메서드가 그 필드를 그대로 반환하게 해서는 안된다.
- 생성자, 접근자, readObject 메서드 모두에서 방어적 복사를 수행해라
public final class Person {
private final Address address;
public Person(Address address) {
this.address = address;
}
public Address getAddress() {
return address;
}
public static void main(String[] args) {
Address seattle = new Address();
seattle.setCity("Seattle");
Person person = new Person(seattle);
Address redmond = person.getAddress();
redmond.setCity("Redmond");
System.out.println(person.address.getCity());
}
}
package me.whiteship.chapter04.item17.part1;
public class Address {
private String zipCode;
private String street;
private String city;
public String getZipCode() {
return zipCode;
}
public void setZipCode(String zipCode) {
this.zipCode = zipCode;
}
public String getStreet() {
return street;
}
public void setStreet(String street) {
this.street = street;
}
public String getCity() {
return city;
}
public void setCity(String city) {
this.city = city;
}
}
위와 같이 adress의 레퍼런스는 final이지만 adress의 정보들은 final이 아닐 경우가 발생할 수 있다. 이러한 경우 방어적 복사를 통해 해결하도록 하자.
public Address getAddress() {
Address copyOfAddress = new Address();
copyOfAddress.setStreet(address.getStreet());
copyOfAddress.setZipCode(address.getZipCode());
copyOfAddress.setCity(address.getCity());
return copyOfAddress;
}
불변 객체
- 함수형 프로그래밍에 적합하다.
- 불변 객체는 단순하다.
- 생성 시점의 상태를 파괴될 때까지 간직한다.
- 모든 생성자가 클래스 불변식(class invariant)을 보장한다면 별다른 노력 없이 영원히 불변으로 남는다. 반면 가변 객체는 복잡한 상태에 놓일 수도 있다.
- 불변 객체끼리는 내부 데이터를 공유할 수 있다.
- 불변 객체는 근복적으로 스레드 안전하여 따로 동기화할 필요 없다.
- 그 어떤 스레드도 다른 스레드에 영향을 줄 수 없으니 안심하고 공유할 수 있다.
- 가장 쉬운 불변 클래스의 인스턴스 재활용 방법은 자주 쓰이는 값들을 상수(public static final)로 제공하는 것이다.
- 불변 클래스는 자주 사용되는 인스턴스를 캐싱하여 같은 인스턴스를 중복 생성하지 않게 해주는 정적 팩터리를 제공할 수 있다.
- 정적 팩터리를 사용하면 여러 클라이언트가 인스턴스를 공유하여 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.
- 새로운 클래스를 설계할 때 public 생성자 대신 정적 팩터리를 만들어두면, 클라이언트를 수정하지 않고도 필요에 따라 캐시 기능을 나중에 덧붙일 수 있다.
- 불변 객체는 방어적 복사가 필요 없다.
- 불변 객체는 그 자체로 실패 원자성을 제공한다.
실패 원자성이란 ‘메서드에서 예외가 발생한 후에도 그 객체는 여전히 (메서드 호출 전과 똑같은) 유효한 상태여야 한다’는 성질이다.
불변 객체의 단점
- 불변 객체는 값이 다르다면 반드시 독립된 객체로 만들어야 한다.
- 값의 가짓수가 많다면 이들을 모두 만드는 데 큰 비용을 치러야 할 수 있다.
- 이에 대처하는 방안은 두 가지이다. 첫 번째는 흔히 쓰일 다단계 연산(multistep operation)들을 예측하여 기본 기능으로 제공하는 방법이다.
- 클라이언트가 원하는 복잡한 연산들을 정확히 예측할 수 있다면 이러한 다단계 연산 속도를 높여주는 package-private인 가변 동반 클래스(companion class)만으로도 충분하다.
- 예측이 불가능하다면 가변 동반 클래스를 public으로 제공하는 게 최선이다.
- ex. StringBuilder, StringBuffer ..
정리
- 클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
- 단순한 값 객체는 불변으로 만들도록 하자.
- 성능 때문에 어쩔 수 없다면 불변 클래스와 함께 가변 동반 클래스를 public 클래스로 제공하도록 하자.
- 모든 클래스를 불변으로 만들 수는 없다. 하지만 불변으로 만들 수 없는 클래스라도 변경할 수 있는 부분을 최소한으로 줄이도록 하자.
- 합당한 이유가 없다면 모든 필드는 private final이어야 한다.꼭 변경해야 하는 필드를 제외한 나머지 모두를 final로 선언하도록 하자.
- 생성자는 불변식 설정이 모두 완료된, 초기화가 완벽히 끝난 상태의 객체를 생성해야 한다.