.getDeclaredConstructor() + setAccessible(true)로 호출할 수 있다. (.newInstance())
생성자 내에서 필드 값을 검증하고, (UnsupportedOperationException)을 던지도록 한다.
리플렉션으로 싱글톤을 깨뜨리는 방법을 막는 방법이다. (더 이상 간결하지는 않다.)
에러 메세지를 잘 읽는 버릇을 가지지 않으면 5년, 10년 뒤에도 에러를 해결하지 못할 것이다.
단점 3. 역직렬화 할 때 새로운 인스턴스가 생길 수 있다.
Serializable 인터페이스를 implement하여 직렬화,역직렬화하기 때문에 readResolve()(readObject())를 사용하게 되는데, 이때 클래스 내에서 재정의하여 역직렬화에서 새로운 객체를 생성하는 것을 막을 수 있다. (역시 간결하다는 장점이 반감되는 방법이다.)
첫번째 방법 예시 코드
public class Elvis implements IElvis, Serializable {
public static final Elvis INSTANCE = new Elvis();
private static boolean created;
// 리플렉션에 의해 싱글톤이 깨지는 것을 막기 위한 방법
private Elvis() {
if (created) {
throw new UnsupportedOperationException("can't be created by constructor.");
}
created = true;
}
public void sing() {
System.out.println("I'll have a blue~ Christmas without you~");
}
private Object readResolve() {
return INSTANCE;
}
}
public class ElvisReflection {
public static void main(String[] args) {
try {
Constructor<Elvis> defaultConstructor = Elvis.class.getDeclaredConstructor();
defaultConstructor.setAccessible(true);
// 리플렉션에 의해 싱글톤이 깨지는 것을 막기 위한 방법을 사용하지 않으면 새로운 인스턴스가 2개 생성된다.
Elvis elvis1 = defaultConstructor.newInstance();
Elvis elvis2 = defaultConstructor.newInstance();
Elvis.INSTANCE.sing();
} catch (InvocationTargetException | NoSuchMethodException | InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
}
}
두번째 방법: private 생성자 + 정적 팩터리 메서드
장점 1. API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다.
클라이언트에서는 .getInstance()를 그대로 사용하면서, 싱글톤을 사용하는 코드에서 매번 새 객체를 만들어 주게 할 수 있다.
장점 2. 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다.
해시코드가 같고 같은 같은 객체임에도 "==" 비교는 할 수 없다.
장점 3. 정적 팩터리의 메서드 참조를 공급자(Supplier)로 사용할 수 있다.
인자 없는 메소드를 호출해서 어떤 Object를 리턴해주는 것이 Supplier이기 때문이다.
private 생성자 + 정적 팩터리 메서드는 supplier의 구현체처럼 사용이 가능하다.
단점은 "첫번째 방법"과 동일하다.
두번째 방법 예시 코드
장점 1. 예시 코드
public class Elvis implements Singer {
private static final Elvis INSTANCE = new Elvis();
private Elvis() { }
// public static Elvis getInstance() { return new Elvis(); }
public static Elvis getInstance() { return INSTANCE; }
public void leaveTheBuilding() {
System.out.println("Whoa baby, I'm outta here!");
}
장점 2. 예시 코드
public class MetaElvis<T> {
private static final MetaElvis<Object> INSTANCE = new MetaElvis<>();
private MetaElvis() { }
// 왜 <T>가 또 선언 되었는가? : 둘의 scope이 다르기 때문이다. MetaElvis<T>에는 클래스에서 정의한 <T>를 사용하는게 아니다.
// public static <T> MetaElvis<T> getInstance() { return (MetaElvis<T>) INSTANCE; }
// 그 예로 <E>로 바꾸어도 아무런 문제가 되지 않는다.
@SuppressWarnings("unchecked")
public static <E> MetaElvis<E> getInstance() { return (MetaElvis<E>) INSTANCE; }
public void say(T t) {
System.out.println(t);
}
public static void main(String[] args) {
// 둘은 동일한 인스턴스이지만 각자 다른 타입으로 사용할 수 있는 경우.
MetaElvis<String> elvis1 = MetaElvis.getInstance();
MetaElvis<Integer> elvis2 = MetaElvis.getInstance();
System.out.println(elvis1);
System.out.println(elvis2);
elvis1.say("hello");
elvis2.say(100);
}
}
장점 3. 예시 코드
public class Concert {
public void start(Supplier<Singer> singerSupplier) {
Singer singer = singerSupplier.get();
singer.sing();
}
public static void main(String[] args) {
Concert concert = new Concert();
concert.start(Elvis::getInstance);
// concert.start(() -> Elvis.getInstance());
}
}
세번째 방법: 열거 타입
가장 간결한 방법이며 직렬화와 리플렉션에도 안전하다.
Enum은 태생적으로 new를 통해 생성을 허용하지 않는다. (리플렉션에도 이가 정의되어 있기 때문에 안전하다.)
테스트는 인터페이스를 implement하는 방법으로 해결할 수 있다.
대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.
세번째 방법 예시 코드
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() {
System.out.println("기다려 자기야, 지금 나갈께!");
}
}
완전 공략
p23, 리플렉션 API로 private 생성자 호출하기
p24, 메서드 참조를 공급자로 사용할 수 있다.
p24, Supplier, 함수형 인터페이스
p24, 직렬화, 역직렬화, Serializable, transient
메소드 참조
메소드 하나만 호출하는 람다 표현식을 줄여쓰는 방법
스태틱 메소드 레퍼런스
인스턴스 메소드 레퍼런스
임의 객체의 인스턴스 메소드 레퍼런스
생성자 레퍼런스
함수형 인터페이스
자바가 제공하는 기본 함수형 인터페이스
함수형 인터페이스는 람다 표현식과 메소드 참조에 대한 “타겟 타입”을 제공한다.
타겟 타입은 변수 할당, 메소드 호출, 타입 변환에 활용할 수 있다.
자바에서 제공하는 기본 함수형 인터페이스 익혀 둘 것. (java.util.function 패키 지)
함수형 인터페이스를 만드는 방법.
심화 학습 1) Understanding Java method invocation with invokedynamic
심화 학습 2) LambdaMetaFactory
객체 직렬화
객체를 바이트스트림으로 상호 변환하는 기술
바이트스트림으로 변환한 객체를 파일로 저장하거나 네트워트를 통해 다른 시스템 으로 전송할 수 있다.
Serializable 인터페이스 구현
transient를 사용해서 직렬화 하지 않을 필드 선언하기 serialVersionUID는 언제 왜 사용하는가?
아이템 3. 생성자나 열거 타입으로 싱글턴임을 보증하라.
첫번째 방법: private 생성자 + public static final 필드
.getDeclaredConstructor()
+setAccessible(true)
로 호출할 수 있다. (.newInstance()
)readResolve()
(readObject())를 사용하게 되는데, 이때 클래스 내에서 재정의하여 역직렬화에서 새로운 객체를 생성하는 것을 막을 수 있다. (역시 간결하다는 장점이 반감되는 방법이다.)첫번째 방법 예시 코드
두번째 방법: private 생성자 + 정적 팩터리 메서드
.getInstance()
를 그대로 사용하면서, 싱글톤을 사용하는 코드에서 매번 새 객체를 만들어 주게 할 수 있다.두번째 방법 예시 코드
장점 1. 예시 코드
장점 2. 예시 코드
장점 3. 예시 코드
세번째 방법: 열거 타입
세번째 방법 예시 코드
완전 공략
메소드 참조
메소드 하나만 호출하는 람다 표현식을 줄여쓰는 방법
함수형 인터페이스
자바가 제공하는 기본 함수형 인터페이스
객체 직렬화
객체를 바이트스트림으로 상호 변환하는 기술