728x90
1. 싱글턴이란?
싱글턴(singleton) : 인스턴스를 오직 하나만 생성할 수 있는 클래스
- 함수와 같은 무상태(stateless) 객체 - 정적 멤버클래스 이야기인가? (Enum 같은거?)
- 설계상 유일해야하는 시스템 컴포넌트
- 클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기 어려울 수 있다. : mock 대체가 불가능
2. 방식 1) public static 멤버가 final 필드
Private 생성자가 public static final 필드를 초기화 할 때 딱 한번만 호출된다.
예외 ) 권한이 있는 클라이언트가 리플렉션 API인 AccessibleObject.setAccessible을 이용해 private 생성자를 호출
방어 ) 생성자를 수정해 두 번째 객체 생성때 예외 던지기
public class Elvis {
public static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public void leaveTheBuilding() {...}
}
[ 장점 ]
- 이 클래스가 싱글턴임이 API에 명백히 들어난다.
- 간결하다.
3. 방식 2) 정적 팩터리 메서드를 public static 멤버로 제공
Elvis.getInstance는 항상 같은 객체의 참조를 반환한다. (리플렉션 예외는 똑같이 적용)
public class Elvis {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {...}
}
[ 장점 ]
- API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.
- 정적 팩터리를 제네릭 싱글턴 팩터리로 만들수 있다. (아이템 30)
- 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다. (아이템 43,44) [예: Instant::now]
4. 방식 3) Enum 타입 방식의 싱글턴
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {...}
}
[ 장점 ]
- 간결하다
- 추가 노력 없이 직렬화가 가능하다.
- 직렬화나 리플렉션 공격에서도 제2의 인스턴스가 생기는 일을 막아준다.
조건 : 싱글턴이 부모클래스가 되어야하는 상황이 생기면 사용할 수 없다.
"대부분 상황에서 원소가 하나뿐인 Enum이 싱글턴을 만드는 가장 좋은 방법이다."
5. 싱글턴 클래스의 직렬화
Serializable 구현한다고 선언하는 것만으로는 부족하다. 인스턴스 필드를 transient라고 선언하고 readResolve 메서드를 제공해야한다. (readResolve에서 인스턴스를 반환한다. - 새로운 인스턴스 생성을 방지한다.)
참조 링크 : https://github.com/Java-Bom/ReadingRecord/issues/4
추가1. 제네릭 싱글턴 팩터리
public class GenericSingletonFactory {
// 코드 30-4 제네릭 싱글턴 팩터리 패턴 (178쪽)
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
// 코드 30-5 제네릭 싱글턴을 사용하는 예 (178쪽)
public static void main(String[] args) {
String[] strings = { "삼베", "대마", "나일론" };
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.println(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
}
코드 요구사항 : IDENTITY_FN를 UnaryOperator<T>로 형변환 하면 비검사 형변환 경고가 발생 Object는 T가 아니기 때문. 그러나 여기 요구사항에서 항등함수를 담은 클래스를 만들고 싶은 것이기 때문에, 비검사 형변환 경고를 숨겨도 안심할 수 있다.
추가2. 메서드 참조 & 공급자
아이템 43 참고
메서드 참조 유형 | 예 | 같은 기능을 하는 람다 |
정적 | Integer::parseInt | str -> Integer.parseInt(str) |
한정적 (인스턴스) | Instant.now()::isAfter | Instant then = Instatn.now(); t->then.isAfter(t); |
비한정적 (인스턴스) | String::toLowerCase | str -> str.toLowerCase(); |
클래스 생성자 | TreeMap<K,V>::new | () -> new TreeMap<K,V>(); |
배열 생성자 | int[]::new | len -> new int[len] |
공급자 = Supplier
인터페이스 명 | 추상 메서드 | 설명 |
Supplier<T> | T get() | T 객체를 리턴한다. |
'Dev Book Review > Effective Java' 카테고리의 다른 글
[Effective Java] item6. 불필요한 객체 생성을 피하라 (0) | 2020.04.15 |
---|---|
[Effective Java] item5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라 (0) | 2020.04.15 |
[Effective Java] item4. 인스턴스화를 막으려거든 private 생성자를 사용하라 (0) | 2020.04.15 |
[Effective Java] item2. 생성자에 매개변수가 많다면 빌더를 고려해라 (0) | 2020.04.15 |
[Effective Java] item1. 생성자 대신 정적 팩터리 메서드를 고려하라 (0) | 2020.04.15 |