이렇게 정적 팩터리를 사용해서 자주쓰는 불변객체는 cache해놔서 객체를 계속 생성하는 것보다 메모리를 아낄수 있다.
불변 객체는 자유롭게 공유할 수 있고, client측에서 불변 객체의 상태 변경이 불가능함으로 당연히 복사해서 사용하는 것도 의미가 없다. 따라서 불필요하게 clone 이나 복사 생성자를 제공할 필요가 없다.
불변 객체는 자유롭게 공유할수 있음은 물론, 불변 객체끼리는 내부 데이터를 공유 할 수 있다.
BigInteger class는 내부에서 값의 부호와 크기를 따로 표현하는데 부호는 int 필드, 크기는 int[] 을 사용한다,
BigInteger의 negate method 는 크기는 동일하고 부호만 반대인 새로운 BigInteger 객체를 생성하는데 이떄 두 불변객체는 가변 타입인 배열을 복사해서 사용하지 않고, 원본 객체와 공유해서 사용한다.
객체를 만들떄 다른 불변 객체들을 구성요소로 사용하면 이점이 많다.
값이 변경되지 않는 구성요소로 이루어진 객체는 불변식을 유지하기 쉽다. 예를 들어 Map,Set 의 경우에 key 또는 원소로 불변 객체를 사용할 경우 안에 담긴 값이 바뀌면 불변식이 허물어지는데 불변 객체를 사용하면 그런 걱정이 필요 없다.
불변 객체는 그 자체로 실패 원자성을 제공한다.
값이 변경 되지 않으므로, 잠깐이라도 불일치 상태에 빠질 가능성이 없다.
불변 객체의 단점
값이 다르면 반드시 독립된 객체로 만들어야 한다.
원하는 객체를 완성하기 까지의 중간 단계가 많고, 객체의 필드를 하나만 바꾸더라도 객체를 새로 생성해야 한다. (객체 생성 비용 )
해결방안
다단계연산을 예측하여 기본기능으로 제공하는 방법
package-private 의 가변 동반 클래스 사용 ex) String의 String Builder
상속 제한 방법
클래스가 불변임을 보장하려면 자신을 상속하지 못하게 해야 한다.
final 클래스 선언
모든 생성자를 private 혹은 package-private로 만들고, 정적 팩토리 메소드 사용
2번 방식은 자식 클래스 생성자에서 부모 클래스 생성자 호출이 불가능함으로 당연히 상속이 불가능해진다. 예시는 다음과 같다.
1 2 3 4 5 6 7 8 9
// private로 외부 호출을막고, 정적팩토리로만 인스턴스를 생성하게 한다. privateComplex(double re, double im){ this.re = re; this.im = im; }
publicstatic Complex valueOf(double re, double im){ returnnew Complex(re,im); }
추가로 BigInteger , BigDecimal의 경우는 final 이 아니고, 상속이 가능하다. 즉 보안상 BigInteger 인지
overriding하여 사용한 가짜 BigInteger인지 확인해야 한다.
1 2 3 4
publicstatic BigInteger isSafeInstance(BigInteger val){ return val.getClass() == BigInteger.class ? val : new BigInteger(val.toByteArray()); }
정리
class는 꼭 필요한 경우가 아니면 불변이여야 하며, 불변 클래스의 장점은 Thread-Safe하다는 점과, 복사를 할 필요도 없고, 프로그래밍시에 가변 클래스보다 안전하다는 장점을 가지고 있다. 반면 객체의 상태가 많고 특정 상태만 조금씩만 변경하고자 할떄는 객체 생성 비용 측면에서 단점을 가지고 있다.
만약 불변으로 만들 수 없는 상황이라면 변경할 수 있는 부분을 최소한으로 줄임으로서, 객체를 예측하고 디버깅하기 쉬워진다.