불변 클래스는 서버가 내려갈 때까지(객체가 파괴되는 순간) 인스턴스 내부의 값이 절대로 바뀌지 않는 클래스다. 자바도 String, primitive types(int, char, double, float 등) 의 박싱된 클래스, BigInteger, BigDecimal 등이 자바에서 제공하는 불변 클래스다. 불변 클래스는 설계 및 구현이 쉽고 오류가 생길 가능성이 적다. *가변 클래스는 프로그램을 작성하면서 값이 변경되고 잘 못 변경된 값으로 인해 시스템 오류가 생길 수 있으나 불변 클래스는 소멸될 때까지 값이 변하지 않아 값 변경으로 인한 오류가 발생할 여지가 없다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 생성자, 접근자, readObject 메서드(아이템 88) 모두에서 방어적 복사를 수행해야한다.
인스턴스 자신은 수정하지 않고 새로운 인스턴스를 만들어 반환하는 프로그래밍 패턴을 함수형 프로그래밍이라 한다. 이와 달리 절차적 혹은 명령형 프로그래밍에서는 메서드에서 피연사자인 자신을 수정해 자신의 상태가 변하게 된다. 불변 객체는 thread safe하여 따로 동기화할 필요가 없다. 이러한 특성을 활용하여 불변 객체는 최대한 reuse를 하면 메모리 사용량과 가비지 컬렉션 비용이 줄어든다.(성능이 좋아진다.) 불변 클래스는 자주 사용되는 인스턴스를 정적 팩터리를 사용하여 캐싱할 수 있다.
* 불변 객체의 단점 : 값이 다르면 반드시 독립된 객체로 만들어야한다. 불변 객체의 값의 가짓수가 많으면 이들을 만드는데 많은 비용이 든다. 그래서 BigInteger에서는 내부적으로 가변 동반 클래스를 사용하여 연산 속도를 높여준다. String 클래스는 새로운 문자열이 생성될 때마다 상수화된 문자열 객체를 만들기에 잦은 변경이 일어날 경우 가변 동반 클래스인 StringBuilder를 사용하면 된다.
가변 클래스도 객체가 가질 수 있는 상태의 수를 줄여 예측하기 쉽고 오류가 생길 여지를 차단하는게 좋다. 이를 위해 꼭 변경해야 할 필드를 뺀 나머지 모두를 final로 선언하자. 객체를 재활용할 목적으로 필드를 초기화하는 메서드를 제공하면 성능 이점 대신 복잡성만 커지고 오류가 발생할 확률이 커진다.
클래스는 꼭 필요한 경우가 아니라면 불변이어야 한다.
뒷 장에서 확인할 내용
불변 객체를 자유롭게 공유할 수 있다는 점은 방어적 복사(아이템 50)도 필요 없다는 결론으로 자연스럽게 이어진다.
불변 객체는 그 자체로 실패 원자성을 제공한다.(아이템 76)
다시 말해 신뢰할 수 없는 하위 클래스의 인스턴스라고 확인되면, 이 인수들을 가변이라 가정하고 방어적으로 복사해 사용해야 한다.(아이템50)
지연 초기화(아이템 803의 예이기도 한 이 기법을 String도 사용한다.
String과 BigInteger처럼 무거운 값 객체도 불변으로 만들 수 있는지 고심해야한다. 성능 때문에 어쩔 수 없다면(아이템 67)불변 클래스와 쌍을 이루는 가변 동반 클래스를 public 클래스로 제공하도록 해야한다.
잘 설계된 컴퓨넌트는 내부 구현 정보를 캡슐화하여 숨기고 외부 컴포넌트는 API를 통해 외부와 정보를 주고 받을 수 있도록 분리되어 설계된다. 정보 은닉이 잘된 시스템은 아래와 같은 장점이 있다.
컴포넌트별 캡슐화가 잘 되어 있어 병렬 개발할 수 있으며 이는 시스템 개발 속도를 높인다.
시스템 관리 비용을 낮춘다. 캡슐화로 인해 오류 발생 시 컴포넌트별로 빠르게 파악할 수 있고 디버깅 시 연관도가 낮아 수정 개발이 편리하다.
성능 최적화를 할 수 있다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정해 해당 컴포넌트만 수정 & 변경을 통해 최적화할 수 있다.
소프트웨어 재사용성을 높일 수 있다.
큰 시스템을 제작하는 난이도를 낮춰준다.
접근 제한자(private, protected, public)와 선언된 위치로 접근성이 제한된다. 접근 제한자를 활용하여 정보 은닉의 이점을 누릴 수 있다. 모든 클래스와 멤버의 접근성을 좁히면 된다.
클래스, 인터페이스의 접근 제한자
package-private
public
패키지 외부에서 쓸 이유가 없다
공개 API가 되어 서비스를 종료할 때까지 계속 버전 관리 필요
다음 릴리즈에서 수정, 교체, 제거 가능
* 외부 공개 API를 제공하는 목적이 아니면 public으로 선언하는 것은 고려해야한다.
접근 제어자(Access Modifier)
private : 멤버를 선언한 클래스에서만 접근할 수 있다.
package-private(default) : 멤버가 소속된 패키지 안의 모든 클래스에서 접근할 수 있다.
protected: package-private의 접근 범위를 포함하고 해당 멤버를 상속한 하위 클래스에서도 접근할 수 있다.
public : 모든 곳에서 접근할 수 있어 공개 API이다.
해당 클래스, 클래스가 속한 패키지내의 구현 범위에 해당하는 private과 package-private는 공개 API에 영향을 주지 않는다. * Serializable을 구현한 클래스에서는 의도와 달리 공개 API가 될 여지가 있다.
테스트를 위해서 멤버 접근 범위를 넓히려 하는데 private -> package-private까지는 괜찮지만 그 이상은 적용하지 않는게 좋다. 테스트를 위함이라면 테스트 코드를 같은 패키지에 두면 package-private 요소에 접근할 수 있기에 package-private은 가능하다.
public 클래스의 인스턴스 필드는 public을 지양하는 게 좋다.
가변 객체를 참조하거나 final이 아닌 경우 필드의 값을 제한할 수 없다.
따로 스레드 처리를 하지 않을 경우 public 인스턴스 필드는 락 획득을 할 수 없어 어느 곳에서나 접근하여 값을 변경할 수 있는 thread safe하지 않다.
예외도 존재하는데 클래스의 추상 개념을 표현하는데 꼭 필요한 구성요소로의 상수라면 public static final필드로 공개해도 좋다. * public static final String DATE_FORMAT 대문자 알파벳으로 작성하며 단어 사이에 밑줄(_)을 작성한다. 이런 타입은 불변 객체나 기본 타입 값을 참조해야한다.
가변 객체를 참조할 경우 setter를 통해 final이 아닌 필드의 값은 변경될 수 있다. 이럴 경우 의도하지 않게 값을 변경할 수 있는 가능성을 열어두게 된다. *final로 인스턴스를 생성했을 때의 동작방식에 대해 살펴보면 알 수 있다.
클래스에서 public static final 배열 필드를 두거나 이 필드를 반환하는 접근자 메서드를 제공해서는 안 된다. * 길이가 0이 아닌 배열은 모두 변경 가능하기 때문이다.
배열을 private으로 만들고 clone()으로 복사본을 반환하는 public static final 메서드를 제공하는 방법
동일하게 배열을 private으로 만들고 public 불변 리스트를 추가
자바 버전 9부터는 모듈 시스템(패키지들의 묶음이며 같은 모듈 안에 패키지들은 자유롭게 공유할 수 있다.)이라는 개념이 도입되면서 public 클래스의 public, protected 멤버에 한하여 같은 모듈 내부에서만 접근 가능하도록 한정했다. 이 같은 개념을 가장 잘 활용하는 사례가 JDK인데 자바라이브러리에서 공개하지 않는 패키지들은 해당 모듈 밖에서는 절대로 접근할 수 없다.
뒷 장에서 확인해야할 내용
한 클래스에서만 사용하는 package-private 톱레벨 클래스나 인터페이스는 이를 사용하는 클래스 안에 private static으로 중첩시켜보자
단 Serializable을 구현한 클래스에서는 그 필드들도 의도치 않게 공개 API가 될 수 있다.(아이템 86,87)