전통적으로 싱글 톤은 일반적으로 다음과 같이 구현됩니다.
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
단순한 직렬화 / 직렬화 전략을 자동으로 수행합니다. 그건 그냥 기다리고 사고입니다. Foo2
t1에서 VM1 의 상태 변수를 개념적으로 참조하는 데이터 트리가 직렬화 / 역 직렬화를 통해 값이 다른 값 ( Foo2
t2에서 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;
}
}