싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.
클래스를 싱글턴으로 만들면 싱글턴 인스턴스를 mock 구현으로 개체할 수 없기 때문에 이를 사용하는 클라이언트를 테스트하기 어려워질 수 있다.
싱글턴을 만드는 방식에 대해 알아보자.
두 방식 모두 생성자는 private으로 감춰둔다.
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
Elvis 클래스의 생성자는 private으로 선언 되었으므로 클래스 내에서만 사용할 수 있다. 따라서 처음에 public static final 필드인 INSTANCE가 초기화될 때 한번만 호출된다. 따라서 Elvis 인스턴스는 시스템 전체에서 INSTANCE 단하나 뿐이다.
예외는 단 한 가지, 권한이 있는 클라이언트는 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있다. 이를 대비하기 위해 생성자에서 두 번째 객체가 생성될 때 예외를 던지면 된다.
리플렉션 API : 타입, 변수명, 파라미터 타입, 수 등등 아주 상세한 클래스 명세정보를 다룰 수 있는 기술
private 필드로 인스턴스를 생성해놓고 public static 메서드를 통해 인스턴스를 가져다 쓰는 방식이다.
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.getInstance();
elvis.leaveTheBuilding();
}
}
getInstance
를 사용해 인스턴스를 가져오고 있는데, getInstance
를 호출하더라도 새로운 인스턴스가 생성되는 것이 아닌, 기존에 생성되어있는 인스턴스를 가져오는 것이다. 이것이 바로 ITEM1에서 살펴본 Boolean의 valueOf에서 사용된 기법이다.
이러한 장점들이 굳이 필요하지 않다면 public 필드의 방식이 좋다.
둘 중 하나의 방식으로 만든 싱글턴 클래스를 직렬화하려면 단순히 Serializable을 구현한다고 선언하는 것만으로는 부족하다.
모든 인스턴스 필드를 일시적(transient)이라고 선언하고 readResolve 메서드를 제공해야한다. 이렇게 하지 않으면 직렬화된 인스턴스를 역직렬화할 때 마다 새로운 인스턴스가 만들어진다.
아래와 같이 추가를 해야한다.
private Object readResolve() {
return INSTANCE;
}
이를 통해 진짜 Elvis를 반환하고, 가짜 Elvis는 가비지 컬렉터에 맡긴다.
가장 바람직한 방법이다.
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
// This code would normally appear outside the class!
public static void main(String[] args) {
Elvis elvis = Elvis.INSTANCE;
elvis.leaveTheBuilding();
}
}
public 필드 방법과 비슷하나, 더 간결하고 추가적인 작업없이 직렬화가 가능하다.
대부분의 상황에서 원소가 하나뿐인 열거타입이 싱글턴을 만드는 가장 좋은 방법이다.
[Java] 리플렉션 API : 클래스, 필드, 메서드 정보 조회 - 코딩하는 흑구
이펙티브 자바 3/E