在Java中实现单例模式的有效方法是什么?[已关闭]

2022-08-31 04:03:23

在Java中实现单例设计模式的有效方法是什么?


答案 1

使用枚举:

public enum Foo {
    INSTANCE;
}

Joshua Bloch在Google I/O 2008的 Effective Java Reloaded 演讲中解释了这种方法:视频链接。另请参阅他的演讲幻灯片30-32(effective_java_reloaded.pdf):

实现可序列化单例的正确方法

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

编辑:“有效Java”的在线部分说:

“这种方法在功能上等同于公共字段方法,除了它更简洁,免费提供序列化机制,并提供针对多个实例化的铁定保证,即使面对复杂的序列化或反射攻击也是如此。虽然这种方法尚未被广泛采用,但单元素枚举类型是实现单例的最佳方式


答案 2

根据用法,有几个“正确”的答案。

从Java 5开始,最好的方法是使用枚举:

public enum Foo {
   INSTANCE;
}

在Java 5之前,最简单的情况是:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

让我们看一下代码。首先,您希望该类是最终类。在这种情况下,我使用了关键字让用户知道它是最终的。然后,您需要将构造函数设为私有,以防止用户创建自己的 Foo。从构造函数中引发异常会阻止用户使用反射创建第二个 Foo。然后,创建一个字段来保存唯一的实例,并创建一个方法来返回它。Java 规范确保仅在首次使用类时才调用构造函数。finalprivate static final Foopublic static Foo getInstance()

当您有一个非常大的对象或繁重的构造代码,并且还有其他可访问的静态方法或字段可能需要在需要实例之前使用时,然后并且只有这样,您才需要使用延迟初始化。

您可以使用 来加载实例。然后,代码将如下所示:private static class

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

由于该行仅在实际使用类 FooLoader 时执行,因此这负责延迟实例化,并保证它是线程安全的。private static final Foo INSTANCE = new Foo();

当您还希望能够序列化对象时,您需要确保反序列化不会创建副本。

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

该方法将确保返回唯一的实例,即使该对象在上次运行的程序中已序列化也是如此。readResolve()


推荐