Java 열거 형으로 싱글 톤을 구현하면 단점은 무엇입니까? 톤은 일반적으로

전통적으로 싱글 톤은 일반적으로 다음과 같이 구현됩니다.

public class Foo1
{
    private static final Foo1 INSTANCE = new Foo1();

    public static Foo1 getInstance(){ return INSTANCE; }

    private Foo1(){}

    public void doo(){ ... }
}

Java 열거 형을 사용하면 단일 톤을 다음과 같이 구현할 수 있습니다

public enum Foo2
{
    INSTANCE;

    public void doo(){ ... }
}

두 번째 버전만큼이나 멋진 점이 있습니까?

(나는 그것에 대해 몇 가지 생각을했고 내 자신의 질문에 대답 할 것입니다.



답변

열거 형 싱글 톤의 일부 문제 :

구현 전략에 전념

일반적으로 “단일”은 API 사양이 아닌 구현 전략을 나타냅니다. Foo1.getInstance()항상 같은 인스턴스를 반환한다고 공개적으로 선언하는 것은 매우 드 rare니다 . 필요한 경우 Foo1.getInstance()예를 들어 스레드 당 하나의 인스턴스를 반환 하도록 구현을 발전시킬 수 있습니다.

함께 Foo2.INSTANCE우리는 공개적으로이 인스턴스가 있음을 선언 예, 그 변화 할 기회가 없습니다. 단일 인스턴스를 갖는 구현 전략이 노출되어 있습니다.

이 문제는 치명적이지 않습니다. 예를 들어 Foo2.INSTANCE.doo()스레드 당 인스턴스 를 효과적으로 갖기 위해 스레드 로컬 도우미 개체를 사용할 있습니다.

열거 형 클래스 확장

Foo2슈퍼 클래스를 확장합니다 Enum<Foo2>. 우리는 보통 슈퍼 클래스를 피하고 싶습니다. 특히이 경우, 강제 수퍼 클래스 는 예상되는 Foo2것과 아무 관련이 없습니다 Foo2. 이것이 애플리케이션의 유형 계층 구조에 대한 오염입니다. 우리가 정말로 슈퍼 클래스를 원한다면 그것은 보통 응용 프로그램 클래스이지만 Foo2수퍼 클래스는 고정되어 있습니다.

Foo2와 같은 재미있는 인스턴스 메소드를 상속합니다.이 메소드 name(), cardinal(), compareTo(Foo2)Foo2의 사용자에게 혼란을 줍니다. 의 인터페이스 에서 해당 메소드가 바람직하더라도 Foo2자체 name()메소드를 가질 수 없습니다 Foo2.

Foo2 재미있는 정적 메소드도 포함되어 있습니다.

    public static Foo2[] values() { ... }
    public static Foo2 valueOf(String name) { ... }
    public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name)

사용자에게는 무의미한 것으로 보입니다. 싱글 톤은 일반적으로 어쨌든 pulbic 정적 방법이 없어야합니다 (이외의 getInstance())

직렬화

싱글 톤이 상태를 유지하는 것은 매우 일반적입니다. 이러한 싱글 톤은 일반적으로 직렬화 할 수 없습니다 . 한 VM에서 다른 VM으로 상태 저장 싱글 톤을 전송하는 것이 의미가있는 현실적인 예는 생각할 수 없습니다. 싱글 톤은 “유니버스에서 고유 한”것이 아니라 “VM 내에서 고유 한”을 의미합니다.

직렬화가 상태 저장 싱글 톤에 실제로 의미가있는 경우 싱글 톤은 동일한 유형의 싱글 톤이 이미 존재할 수있는 다른 VM에서 싱글 톤을 역 직렬화하는 것이 무엇을 의미하는지 명시적이고 정확하게 지정해야합니다.

Foo2단순한 직렬화 / 직렬화 전략을 자동으로 수행합니다. 그건 그냥 기다리고 사고입니다. Foo2t1에서 VM1 의 상태 변수를 개념적으로 참조하는 데이터 트리가 직렬화 / 역 직렬화를 통해 값이 다른 값 ( Foo2t2에서 VM2 의 동일한 변수 값)이되어 버그를 감지하기 어렵습니다. 직렬화 할 수없는이 버그는 Foo1자동으로 발생하지 않습니다 .

코딩 제한

일반 수업에서는 할 수 있지만 수업에서는 할 수없는 일이 있습니다 enum. 예를 들어 생성자의 정적 필드에 액세스합니다. 프로그래머는 특별한 수업을하고 있기 때문에 더 조심해야합니다.

결론

열거 형을 피기 백하면 코드 2 줄을 절약 할 수 있습니다. 그러나 가격이 너무 비싸서 열거 형의 모든 수하물과 제한을 수행해야하며 의도하지 않은 결과를 초래하는 열거 형의 “기능”을 실수로 상속합니다. 유일하게 주장되는 장점 인 자동 직렬화 기능은 단점으로 밝혀졌습니다.


답변

열거 형 인스턴스는 클래스 로더에 따라 다릅니다. 즉, 같은 열거 클래스를로드하는 부모로서 첫 번째 클래스 로더가없는 두 번째 클래스 로더가있는 경우 메모리에 여러 인스턴스를 가져올 수 있습니다.


코드 샘플

다음 열거 형을 만들고 .class 파일을 jar에 넣습니다. (물론 항아리는 올바른 패키지 / 폴더 구조를 갖습니다)

package mad;
public enum Side {
  RIGHT, LEFT;
}

이제이 테스트를 실행하여 클래스 경로에 위 열거 형의 사본이 없는지 확인하십시오.

@Test
public void testEnums() throws Exception
{
    final ClassLoader root = MadTest.class.getClassLoader();

    final File jar = new File("path to jar"); // Edit path
    assertTrue(jar.exists());
    assertTrue(jar.isFile());

    final URL[] urls = new URL[] { jar.toURI().toURL() };
    final ClassLoader cl1 = new URLClassLoader(urls, root);
    final ClassLoader cl2 = new URLClassLoader(urls, root);

    final Class<?> sideClass1 = cl1.loadClass("mad.Side");
    final Class<?> sideClass2 = cl2.loadClass("mad.Side");

    assertNotSame(sideClass1, sideClass2);

    assertTrue(sideClass1.isEnum());
    assertTrue(sideClass2.isEnum());
    final Field f1 = sideClass1.getField("RIGHT");
    final Field f2 = sideClass2.getField("RIGHT");
    assertTrue(f1.isEnumConstant());
    assertTrue(f2.isEnumConstant());

    final Object right1 = f1.get(null);
    final Object right2 = f2.get(null);
    assertNotSame(right1, right2);
}

그리고 이제 “동일한”열거 형 값을 나타내는 두 개의 객체가 있습니다.

나는 이것이 희귀하고 고안된 코너 케이스이며 거의 항상 열거 형을 Java 싱글 톤에 사용할 수 있다는 데 동의합니다. 나는 그것을 스스로한다. 그러나이 질문은 잠재적 인 단점에 대해 물었고이주의 사항은 알아야 할 가치가 있습니다.


답변

생성자에서 예외를 발생시키는 클래스에는 열거 형 패턴을 사용할 수 없습니다. 이것이 필요한 경우 공장을 사용하십시오.

class Elvis {
    private static Elvis self = null;
    private int age;

    private Elvis() throws Exception {
        ...
    }

    public synchronized Elvis getInstance() throws Exception {
        return self != null ? self : (self = new Elvis());
    }

    public int getAge() {
        return age;
    }
}