규칙 3. private 생성자나 enum 타입으로 싱글턴임을 보증하라
[싱글턴](dp-singleton)은 객체를 하나만 만들 수 있는 클래스다.
singleton 구현 방법
public static final 상수(before JDK 1.5)
public class Single {
	public static final Single INSTANCE = new Single();
	private Single() { ... }
}문제점
- 
리플렉션으로 private 생성자 호출 가능 
- 
생성자에서 에러날 경우 예외처리 불가능 → static 초기화 블럭으로 해결 가능 
static factory 메서드(before JDK 1.5)
public class Single {
	private static final Single INSTANCE = new Single();
	private Single() { ... }
	public static Single getInstance() {
		return INSTANCE;
	}
}문제점
- 
리플렉션으로 private 생성자 호출 가능 
- 
위 두 방법에서 [직렬화](#serializable)가능 클래스로 만드려면 클래스 선언에 `implements Serializable`을 추가하는 것으로는 부족하다. 
- 
클래스 선언에 implements Serializable추가
- 
모든 객체 필드에 transient선언
- 
[ readResolve()추가](#item77)```java private Object readResolve() { // 동일한 객체가 반환되도록 하는 동시에, // 가짜 객체는 gc가 처리하도록 만든다. return INSTANCE; } ``` - thread safe하려면 synchronized 적용```java public static synchronized Single getInstance() { ... } ```
Initialization on demand holder idiom
- 
jvm 의 class loader의 매커니즘과 class의 load 시점을 이용하여 내부 class를 생성시킴으로 thread 간의 동기화 문제를 해결 
- 
lazy initialization 
public class Singleton {
	private Singleton() { }
	private static class SingletonHolder {
	        public static final Singleton INSTANCE = new Singleton();
	}
	public static Singleton getInstance() {
	        return SingletonHolder.INSTANCE;
	}
}*enum*을 이용하는 방법(after JDK 1.5)
public enum Single {
	INSTANCE;
	...
}- 
직렬화 자동으로 처리된다. 
- 
리플렉션 공격에도 안전하다. 
- 
Enum 생성은 Thread-safe하지만, 내부 메서드들은 Thread-safe를 보장하지 않는다. 
why?
- 
선언된 상수 이외의 다른 객체는 존재할 수 없다는 확실한 보장이 생긴다(JVM이 해주는 보장). 
- 
enum타입은Comparable인터페이스,Serializable인터페이스가 구현되어 있다.
참고
Link
- 
java singleton pattern (싱글톤 패턴) - https://blog.seotory.com/post/2016/03/java-singleton-pattern 
- 
게으른 홀더를 통한 싱글턴의 동시성 문제 해결 (Initialization on demand holder idiom) - http://changsuk.me/?p=1433 
- 
Thread-safe Enum Singleton - http://stackoverflow.com/questions/28369025/thread-safe-enum-singleton 
serializable
객체의 내용을 파일에 저장하거나 네트워크로 전송하기 위해서 스트림으로 만드는 작업(바이트 단위로 변환)
- 
Serializable인터페이스 구현
- 
모든 필드 또한 Serializable인터페이스 구현
- 
제외하고자하는 필드는 transient
example
public class Test {
	public static void main(String[] args) throws IOException, ClassNotFoundException {
		SerializerTest test = new SerializerTest();
		test.serialization();
		test.deserialization();
	}
}
class SerializerTest {
	private String filePath = "/Users/yeongjun/Desktop/test.ser";
	private User user;
	public void serialization() throws IOException {
		user = new User("yj", 26, "pwd");
		FileOutputStream f = new FileOutputStream(filePath);
		ObjectOutputStream o = new ObjectOutputStream(f); // 직렬화 클래스
		o.writeObject(user); // 파라미터로 넘긴 객체를 스트림으로 만들어서 출력하는 메서드
		o.close();
	}
	public void deserialization() throws IOException, ClassNotFoundException {
		FileInputStream f = new FileInputStream(filePath);
		ObjectInputStream o = new ObjectInputStream(f); // 역직렬화 클래스
		user = (User)o.readObject(); // 입력된 스트림으로부터 객체를 만들어서 반환하는 메서드
		o.close();
		System.out.println(user.toString());
	}
}
class User implements Serializable {
	private static final long serialVersionUID = 1L; // 이건 왜?
	private String name;
	private int age;
	private transient String password;
	public User(String name, int age, String password) {
		this.name = name;
		this.age = age;
		this.password = password;
	}
	@Override
	public String toString() {
		return "User{name='" + name + '\'' + ", age=" + age + ", password='" + password + "\'}";
	}
}