규칙 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 + "\'}";
}
}