본문 바로가기

프로그래밍/JAVA

[JAVA] Effective Java - 3. private 생성자나 열거 타입으로 싱글턴 보증

반응형

싱글턴이란?

인스턴스를 하나만 생성할 수 있는 클래스를 말한다.

상태를 가지지않는 객체나 설계상 유일한 시스템 컴포넌트에 적합하다.

 

싱글턴을 만드는 방법은 3가지 이다

 

1. 유일한 인스턴스에 접근 가능한 public static final

public class Elvis {

  public static final Elvis INSTANCE = new Elvis();

  private Elvis() {
  }

  public void print() {
    System.out.println("Elvis");
  }
}

public static final 필드인 Elvis.INSTANCE를 초기화 할 때 생성자가 한번만 호출된다.

public 이나 protected 생성자가 없으므로 Elvis의 클래스가 초기화할 때 만들어진 인스턴스가 하나임을 보장한다.(Item 4)

장점 1. 싱글턴인게 명백하게 보임

장점 2. 간결함

 

단 리플렉션인 AccessibleObject.setAccessible을 사용해 생성자를 호출 가능하다.

Class cl = Class.forName("Elvis");
AccessibleObject declaredConstructor = cl.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
Constructor conn = (Constructor) declaredConstructor;
Elvis o = (Elvis) conn.newInstance();
o.print();

이러한 공격을 방어하려면 생성자를 수정하여 두번째 객체가 생성되려하면 예외를 던지게 하면 된다.

 

2. 정적 팩터리 메서드를 public static 멤버로 제공한다.

public class Elvis {

  private static final Elvis INSTANCE = new Elvis();
  private Elvis() {}
  public static Elvis getInstance() {
    return INSTANCE;
  }

  public void print() {
    System.out.println("Elvis");
  }
}

Elvis.getInstance는 항상 같은 객체의 참조를 반환하므로 제 2의 인스턴스가 만들어지지 않는다.

(위의 예외와 똑같이 적용되기는함)

장점 1. 수정 하지 않아도 싱글턴이 아니게 확장할 수 있다.

장점 2. 제네릭 싱글턴 팩터리로 만들 수 있다.

  * 제네릭 싱글턴 팩터리란

class Temp {
  private static final List colorList = new ArrayList();
  public static <T> List<T> getColorList() {
    return (List<T>) colorList;
  }
}
List<String> colorList = Temp.<String>getColorList();
List<Integer> colorList1 = Temp.<Integer>getColorList();
colorList.add("RED");
colorList1.add(111111);
System.out.println("colorList = " + colorList.toString()); // colorList = [RED, 111111]

제네릭 타입을 받을 수 있는 객체를 싱글턴으로 만들고,

해당 객체의 정적 팩터리 메서드 호출 시 받은 타입으로 반환 해준다.

 

장점3. 정적 팩터리의 메서드 참조를 공급자로 사용할 수 있다.

정적팩터리 메소드를 Supplier<T> 공급자로 사용할 수 있다.

class Service {
  public static <T> void execute(Supplier<T> supplier) {
    T t = supplier.get();
    // ..
  }
}
Service.execute(Elvis::getInstance);

 

3. 원소가 하나인 열거타입을 선언

public enum Elvis {
  INSTANCE;

  public void print() {
    //...
  }
}

public 필드 방식과 유사하지만, 더 간결하고, 직렬화에 작업이 필요없고, 리플렉션 공격에도 완전히 막아준다.

책에선 가장 좋은방법이라고 소개 되어있다.

클래스 상속을 못하는 단점이 있다.

 

* 싱글턴 객체 직렬화

직렬화란 객체를 다른 시스템에서 사용하기 위해 객체를 바이트화 하는것을 말한다.

이때, 클래스는 Serializable 인터페이스를 구현한다.

싱클턴 클래스에 단순히 Serializable만 선언시 직렬화 후 다시 역직렬화 하면 새로운 인스턴스가 만들어진다.

추가로

public Object readResolve() {
  return INSTANCE;
}

readResolve 메서드를 추가해주면 역직렬화 시에도 싱글턴임이 보장된다.

반응형