이펙티브 자바 아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보장하라 final 키워드로 싱글톤임을 보장함 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package  com.github.sejoung.codetest.singleton;import  java.io.Serializable;class  OldSingleton  implements  Serializable  {         private  static  final  long  serialVersionUID  =  -4253142440722917903L ;          final  static  String  NAME  =  new  String ("OldSingleton" );               final  static  OldSingleton  INSTANCE  =  new  OldSingleton ();     private  OldSingleton ()  {         System.out.println("hi" );     } } 
위에 코드는 기본적으로 final 키워드로 싱글톤임을 보장한다. 
정적 팩토리 패턴으로 싱글턴임을 보장 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 package  com.github.sejoung.codetest.singleton;import  java.io.Serializable;class  OldSingleton  implements  Serializable  {         private  static  final  long  serialVersionUID  =  -4253142440722917903L ;          final  static  String  NAME  =  "OldSingleton" ;               private  final  static  OldSingleton  INSTANCE  =  new  OldSingleton ();     private  OldSingleton ()  {         System.out.println("hi" );     }     public  static  OldSingleton getInstance ()  {         return  INSTANCE;     }      } 
위에 코드는 싱글톤 구현에 두번째 방법인 정적 팩토리 패턴으로 싱글턴을 구현한 예제이다.
위에 코드들는 문제 점이 있는데
OldSingleton의 INSTANCE를 사용하지 않고 위에 NAME만 사용해도 인스턴스가 생성이 된다.
그단점을 보안한게 Bill Pugh Singleton Implementation인데 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 package  com.github.sejoung.codetest.singleton;import  java.io.Serializable;class  LazySingleton  implements  Serializable  {         private  static  final  long  serialVersionUID  =  2648649688472510437L ;          final  static  String  NAME  =  "LazySingleton" ;               private  final  static  class  LazySingletonHolder  {         private  final  static  LazySingleton  INSTANCE  =  new  LazySingleton ();     }     private  LazySingleton ()  {         System.out.println("hi" );     }     public  static  LazySingleton getInstance ()  {         return  LazySingletonHolder.INSTANCE;     } } 
위에처럼 Holder를 사용해서 초기화 한다 
위에 코드들은 그럼 싱글톤임을 보장하는가 보장하지 못하는 케이스가 있다. 
그것은 직렬화 하고 복호화 시켰을때 보장하지 못하는 케이스가 있다.
그것을 해결하는 방법으로 readResolve를 구현하는 것이 있다.
위에 코드의 문제점들을 테스트 해보는 코드는 이렇게 작성했다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 package  com.github.sejoung.codetest.singleton;import  java.io.ByteArrayInputStream;import  java.io.ByteArrayOutputStream;import  java.io.ObjectInputStream;import  java.io.ObjectOutputStream;public  class  SingletonTest  {    public  static  void  main (String[] args)  {                  try  {             System.out.println("1======================" );             System.out.println(OldSingleton.NAME);             System.out.println("2======================" );             OldSingleton  oldSingleton1  =  OldSingleton.getInstance();             System.out.println("3======================" );             OldSingleton  oldSingleton2  =  OldSingleton.getInstance();             System.out.println("4======================" );             System.out.println(serializeAndUnSerialize(oldSingleton1) == serializeAndUnSerialize(oldSingleton2));             System.out.println("1======================" );             System.out.println(LazySingleton.NAME);             System.out.println("2======================" );             LazySingleton  lazySingleton1  =  LazySingleton.getInstance();             System.out.println("3======================" );             LazySingleton  lazySingleton2  =  LazySingleton.getInstance();             System.out.println("4======================" );             System.out.println(serializeAndUnSerialize(lazySingleton1) == serializeAndUnSerialize(lazySingleton2));         } catch  (Exception e) {             e.printStackTrace();         }            }     public  static  <T> T serializeAndUnSerialize (T o)  throws  Exception {                  byte [] serialized;         try  (ByteArrayOutputStream  baos  =  new  ByteArrayOutputStream ()) {             try  (ObjectOutputStream  oos  =  new  ObjectOutputStream (baos)) {                 oos.writeObject(o);                 serialized = baos.toByteArray();             }         }                  T  unSerialized  =  null ;         try  (ByteArrayInputStream  bais  =  new  ByteArrayInputStream (serialized)) {             try  (ObjectInputStream  ois  =  new  ObjectInputStream (bais)) {                 Object  object  =  ois.readObject();                 unSerialized = (T) object;             }         }         return  unSerialized;     } } 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 1====================== hi OldSingleton 2====================== 3====================== 4====================== false 1====================== LazySingleton 2====================== hi 3====================== 4====================== false Process finished with exit code 0 
위에 코드에서 보면 시리얼라이즈 했다가 다시 언시리얼라이즈 한코드가 있는데
위에 문제를 해결하기 위해서 LazySingleton 코드를 조금 수정해보겠다. 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 package  com.github.sejoung.codetest.singleton;import  java.io.ObjectStreamException;import  java.io.Serializable;class  LazySingleton  implements  Serializable  {         private  static  final  long  serialVersionUID  =  2648649688472510437L ;          final  static  String  NAME  =  "LazySingleton" ;               private  final  static  class  LazySingletonHolder  {         private  final  static  LazySingleton  INSTANCE  =  new  LazySingleton ();     }     private  LazySingleton ()  {         System.out.println("hi" );     }     public  static  LazySingleton getInstance ()  {         return  LazySingletonHolder.INSTANCE;     }     Object readResolve ()  throws  ObjectStreamException {         return  LazySingletonHolder.INSTANCE;     } } 
위에 코드를 보면 readResolve()를 구현했는데 이렇게 구현하고 다시 한번 위에 테스트 코드를 작성 하면 싱글톤임을 보장한다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 1====================== hi OldSingleton 2====================== 3====================== 4====================== false 1====================== LazySingleton 2====================== hi 3====================== 4====================== true Process finished with exit code 0 
그리고 private 생성자로 생성자를 잠궈도 호출할수 있는 방법이 존재하는데 java 의 reflect을 통해서 호출할수 있다.
아래는 호출 방법에 대한 코드이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package  com.github.sejoung.codetest.singleton;import  java.lang.reflect.Constructor;import  java.lang.reflect.InvocationTargetException;public  class  AccessibleObjectTest  {    public  static  void  main (String[] args)  throws  NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {         OldSingleton  a1  =  OldSingleton.getInstance();         Constructor<OldSingleton> c = OldSingleton.class.getDeclaredConstructor();         c.setAccessible(true );         OldSingleton  a2  =  c.newInstance();         System.out.println(a1 == a2);     } } 
위에 코드를 실행해서 보면
1 2 3 4 5 6 7 8 hi hi false Process finished with exit code 0 
생성자가 2번 호출이 되고 객체 비교가 false로 나온다.
음 위에 코드의 방어를 한다고 하면 OldSingleton 바꿔야 되는데 간단한 코드를 작성하면
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 package  com.github.sejoung.codetest.singleton;import  java.io.Serializable;class  OldSingleton  implements  Serializable  {         private  static  final  long  serialVersionUID  =  -4253142440722917903L ;     private  static  int  CNT  =  0 ;          final  static  String  NAME  =  new  String ("OldSingleton" );               private  static  OldSingleton  INSTANCE  =  null ;     static  {         try  {             INSTANCE = new  OldSingleton ();         } catch  (Exception e) {             e.printStackTrace();         }     }     private  OldSingleton ()  throws  Exception {         if (CNT>0 ){             throw  new  AssertionError ();         }         System.out.println("hi" );         CNT++;     }     public  static  OldSingleton getInstance ()  {         return  INSTANCE;     } } 
위처럼 다시 코드를 바꾸고 테스트 코드를 실행 시키면 
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 hi Exception in thread "main" java.lang.reflect.InvocationTargetException 	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) 	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) 	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) 	at java.lang.reflect.Constructor.newInstance(Constructor.java:423) 	at com.github.sejoung.codetest.singleton.AccessibleObjectTest.main(AccessibleObjectTest.java:15) Caused by: java.lang.Exception 	at com.github.sejoung.codetest.singleton.OldSingleton.<init>(OldSingleton.java:31) 	... 5 more Process finished with exit code 1 
처음 생성자는 정상인데 두번째 부터 익셉션을 만든다.
enum으로 싱글톤을 보장 이모든 작업을 하기 어려워서 3번째 enum 클래스를 생성해서 하는게 위에서 말한 코드를 가장 편하게 방어 할수있는 코드이다.
1 2 3 4 5 6 7 8 9 10 11 12 package  com.github.sejoung.codetest.singleton;public  enum  EnumSingleton  {    INSTANCE;     public  void  test ()  {         System.out.println("test" );     } } 
위처럼 만들면 위에서 처리 했던내용을 가장 간단하게 구현할수 있다.
단점은 enum 외 다른클래스를 상속받지 못한다.
extends는 안되고 implements는 가능함
참조