(Effective Java) 변경가능성을 최소화 하라

* 변경 불가능(immutable) 클래스는 그 객체를 수정할 수 없는 클래스다.
객체 내부의 정보는 객체가 생성될 때 주어진 것이며, 객체가 살아있는 동안 그대로 보존된다.
예) String, 기본자료형 클래스, BigInteger, BigDecimal 등이다.

이러한 클래스를 만드는 이유는 설계하기 쉽고, 구현하기 쉽고, 오류가능성도 적고, 더 안전하다.

* 변경 불가능 클래스를 만들 때 5가지 규칙

1. 객체 상태를 수정하는 메소드(수정자mutator 메소드 등)를 제공하지 않는다.
2. 계승할 수 없도록 한다. : 보통 클래스를 final로 선언하면 된다.
3. 모든 필드를 final로 선언한다. : 새로 생성된 객체에 대한 참조가 동기화(synchronization)없이 다른 스레드로 전달되어도 안전하다.
4. 모든 필드를 private으로 선언한다.
5. 변경가능 컴포넌트에 대한 독점적 접근권을 보장한다. : 클래스에 포함된 변경 가능 객체에 대한 참조를 클라이언트는 흭득할 수 없어야 한다.





-> this 객체를 변경하는 대신 새로운 Complex객체를 만들어 반환하도록 구현되어있다. 대부분의 변경 불가능 클래스가 따르는 패턴이다.
이러한 방법을 함수형접근법이라하고,  변경불가능성을 보장하므로 장점이많다.


* 또한 변경 불가능한 객체는 스레드에 안전(thread-safe)할 수 밖에 없다. 어떤 동기화도 필요없으며, 여러 스레드가 동시에 사용해도 상태가 훼손될 일이 없다.
따라서 변경 불가능한 객체는 자유롭게 공유할 수 있다.
변경 불가능 클래스는 클라이언트가 기존 객체를 재사용하도록 적극 장려해서 충분히 살릴 필요가 있다.

첫번째. public static final Complex zero =new Complex(0, 0);
두번째. 변경 불가능한 클래스는 자주 사용하는 객체를 캐시(cache)하여 이미 있는 개체가 거듭 생성되지 않도록 정적팩토리를 제공할 수 있다.
이런 정적팩토리 메소드를 사용하면 클라이언트는 새로운 객체를 만드는 대신 기존 객체들을 공유하게 되므로 메모리 요구량과 쓰레기 수집비용이 줄어든다.
새로운 클래스를 설계할 때 public 생성자 대신 정적 팩토리 메소드를 제공하면 나중에 클라이언트 코드를 변경하지 않고도 캐시 기능을 추가할 수 있다.

변경 불가능한 객체를 자유롭게 공유할 수 있다는 것은, 방어적 복사본을 만들 필요가 없단 뜻이다. 따라서 변경 불가능한 클래스에 clone 메소드나 복사 생성자는 만들 필요도 없고 만들어서도 안된다.



* 변경 불가능 클래스를 구현하는 방법은
보통은 클래스를 final로 선언한다. 그보다 더 유연한 방법은 모든 생성자를 private나 package-private로 선언하고 public 생성자 대신 public 정적 팩토리를 제공하는 것이다.

* 한 가지 주의할 것은 직렬화(serialization)부분이다. 변경 불가 클래스가 Serialization인터페이스를 구현하도록 했고, 해당 클래스에 변경가능 객체를 참조하는 필드가 있다면, readObject메소드나 readResolve메소드를 반드시 제공해야 한다.
아니면 ObjectOutputStream.writeUnshared나 ObjectOutputStream.readUnshared메소드를 반드시 사용해야 한다.
설사 기본 직렬화 형식이 만족스러워도 그래야 한다. 그렇지 않으면, 시스템 공격자가 변경 불가능 클래스로부터 변경 가능 객체를 만들 수 있게 된다.


The ObjectOutputStream’s writeUnshared method is an alternative to the writeObject method. As the name suggests, this method does not retain or write object references. So, every time an object is written, it is always completely serialized. The ObjectInputStream’s readUnshared method is an alternative to readObject method. Each time, it reads an object in the stream, it deserializes it completely.



  • An object written via writeUnshared is always serialized in the same manner as a newly appearing object (an object that has not been written to the stream yet), regardless of whether or not the object has been written previously.
  • If writeObject is used to write an object that has been previously written with writeUnshared, the previous writeUnshared operation is treated as if it were a write of a separate object. In other words, ObjectOutputStream will never generate back-references to object data written by calls to writeUnshared.


* 요약하자면, 모든 get메소드마다 그에 대응하는 set 메소드를 두는 것은 피해야 한다. 변경 가능한 클래스로 만들 타당한 이유가 없다면, 반드시 변경 불가능 클래스로 만들어야 한다.

* 변경 불가능한 클래스로 만들수없다면, 변경 가능성을 최대한 제한하라. 객체가 가질 수 있는 상태의 가짓수를 줄이면 객체상태를 추론하기 쉬워지고 오류 가능성도 줄어든다. 따라서 특별한 이유가 없다면 모든필드는 final로 선언하라.


댓글

이 블로그의 인기 게시물

(18장) WebSocekt과 STOMP를 사용하여 메시징하기

(네트워크)폴링방식 vs 롱 폴링방식

(ElasticSearch) 결과에서 순서 정렬