이펙티브 자바 - 아이템 3 & 4
이 글은 몇몇 크루들과 이펙티브 자바 스터디를 하며 정리한 내용입니다. 🙌
🌩 [아이템 3] private 생성자나 열거 타입으로 싱글턴임을 보증하라
하나의 인스턴스만 생성할 수 있는 것이 싱글턴(Singleton) 패턴이다. 참고로 싱글턴을 사용할 경우 이것을 사용하는 클라이언트를 테스트하기가 어렵다. 싱글턴 객체의 생성지점을 제어하기 어려우므로 mock으로 대체하기가 어렵기 때문이다. 싱글턴 객체를 구현하기 위해서는 다음 3가지 방법을 사용할 수 있다.
1) private 생성자 & public static final 필드
public class Earth {
public static final Earth INSTANCE = new Earth();
private Earth() {
...
}
}
생성자의 접근제어자가 private이므로 인스턴스는 오직 INSTANCE
필드를 초기화할 때 단 한번만 생성된다. 일반적인 경우 클라이언트는 이 부분에 대한 권한이 전혀 없지만 예외적으로 AccessibleObject.setAccessible
을 사용한다면 private 생성자를 호출할 수 있기는 하다. (방어를 위해서는 두번째 생성자 호출시 예외발생)
- 장점
public static final
필드를 사용할 경우 싱글턴이라는 것이 분명히 드러난다. (final
이므로 재정의 할 수 없다.)- 간결하다.
2) private 생성자 & 정적 팩터리 메서드
public class Earth {
private static final Earth INSTANCE = new Earth()'
pirvate Earth() {
...
}
public static Earth getInstance() {
return INSTANCE;
}
}
- 장점
- 싱글턴이 아닌 경우로 리팩토링 할 경우 변경에 유연하다.
- 정적 팩터리를 제네링 싱글턴 팩터리로 만들 수 있다.
- 정적 팩터리 메소드를 Supplier로 사용할 수 있다. (일급 함수로 사용 가능)
위 방법에서 직렬화 시 주의할 점
직렬화는 객체를 바이트로 변환해 데이터를 외부 시스템에 영구적으로 저장하거나 사용할 수 있도록 하는 것을 말한다. 직렬화 후 다시 역직렬화 할 때(readObject()
사용시) 싱글톤임에도 불구하고 새로운 인스턴스가 생성된다.
이를 방지하기 위해서는 직렬화 하는 객체의 필드를 transient
선언(직렬화 대상에서 제외하고 readResolve()
메서드를 정의하여 기존에 생성된 객체를 반환하도록 해야 한다. 이때도 readObject()
호출 시 새로운 인스턴스가 생성되기는 하지만 해당 인스턴스를 가짜 인스턴스로 간주하고 무시하여 GC가 처리한다.
private Object readResolve() {
return INSTANCE;
}
3) Enum 타입으로 선언
public enum Earth {
INSTANCE;
...// 관련 메서드 정의
}
열거타입으로 선언할 경우 매우 간결하고 특별한 노력 없이 직렬화 관련 문제도 해결된다. 또한 리플렉션 시에도 싱글톤임을 보장해준다. 하지만 열거 타입의 본래 사용의도와 어긋나므로 어색해보일 수 있다.
🌩 [아이템 4] 인스턴스화를 막으려거든 private 생성자를 사용하라
객체지향적으로 보았을 때 안티패턴이기는 하지만 필요시 정적 필드와 정적 메서드만을 모아둔 utility 성향의 클래스를 생성하게 될 때가 있다.
- 자바의 경우 java.util.Arrays, java.util.Collections와 같이 배열과 관련된 메서드를 모아 놓거나 특정 인터페이스 구현체를 생성해주는 팩터리 역할을 하는 경우 필요하다.
이 경우 클래스의 인스턴스화를 막아야 하는데 이때는 기본 생성자를 private
으로 선언하여 명시해주어야 한다.
- 컴파일러는 기본 생성자가 명시되어 있지 않으면 자동으로 기본 생성자를 만들어서 인스턴스화가 가능하도록 한다.
- 실수로 클래스 내에서 private 생성자를 사용하지 않도록 주의해야 한다.
- 기본 생성자가 막혀있다면 하위 클래스에서 상위 클래스의 생성자에 접근할 수 없으므로 상속을 불가능하게 하는 효과도 있다.
사용하지 않을 생성자를 코드에 명시하는 것이므로 직관적인 코드는 아니라는 단점도 있다.