Java中枚举单例的用途是什么?

7
当四人帮引入单例模式时,他们还必须解释为什么不使用静态类字段和方法。原因是:可能会继承。对于Java来说,这有意义 - 我们通常无法继承类字段和方法。

后来出现了《Effective Java》一书。我们现在知道,反射的存在破坏了具有私有构造函数的单例类的独特性。而使真正的SINGLEton的唯一方法是将其作为枚举的单个项。很好。我自己也用过这种方式。

但问题依然存在:虽然我们不能从枚举中继承,但这个单例有什么用呢?为什么我们不使用那些老旧的好的静态/类字段和方法呢?

编辑。感谢@bayou.io,我看到在https://softwareengineering.stackexchange.com/a/204181/44104中有一个代码可以欺骗枚举,并再次创建两个示例的枚举单例。其他问题也在那里提到。那么,是否也没有必要使用枚举而不是通常的单例类模式呢?顺便说一下,目前为止提到的所有枚举加号都适用于单例类。


3
我认为这个问题并不是那个问题的重复。它不是关于如何或为什么使用枚举来实现单例对象。它是关于为什么要实现一个对象(实例),而不是将所有单例功能都编写为静态方法,并通过类名而不是对象引用访问。 - RealSkeptic
好的,那个概念对我来说太陌生了,我甚至没有考虑过它。 - Sotirios Delimanolis
那个问题只涉及一个常见词 - 单例。就这些了。 - Gangnus
http://programmers.stackexchange.com/questions/179386/what-are-the-downsides-of-implementing-a-singleton-with-javas-enum - ZhongYu
你不能懒加载一个静态类。而且你也不能懒加载一个枚举。所以在这方面它们之间没有区别。我认为你现在把原来的问题和新问题混淆了。似乎有三个选项:(1)使用传统类的单例实例。(2)使用枚举的单例实例。(3)使用静态变量和方法的单例功能。最初,你问的是(3)是否比(2)更好 - 问题是否(2)比(1)更好已经在SO、程序员等中讨论过很多次了。 - RealSkeptic
显示剩余13条评论
4个回答

3
这个单例模式有什么用处?我们为什么不使用旧的静态/类字段和方法呢?
因为枚举类型是一个对象,所以它不仅可以被传递,而且还可以实现接口。
另外,由于我们正在创建一个类,所以我们可以使用所有种类类可用的不同的public/private选项。
因此,在实践中,我们可以创建一个实现接口的单例,然后在我们的代码中传递它,并且调用代码并不知道这一点。我们还可以使枚举类包私有,但仍将其传递到其他包中的其他类中,这些类期望该接口。
如果我们使用静态方法的版本,则调用类必须知道这个对象是单例,而且我们的单例类必须是public,以便其他类可以看到它并使用它的方法。

2

“好老式单例”没有什么特别的问题,枚举“单例”只是方便——它可以节省您与每个单例都相同的样板代码打交道的需要。


你甚至都没有试着回答这个问题 - 枚举单例与类成员/方法相比有什么用处? - Gangnus

1

在我看来,无论何时你想要表示某个东西是其种类中独一无二的,单例模式都是有意义的。

举个例子,如果我们想要建模“太阳”,它不能是一个普通的类,因为只有一个“太阳”。然而,将其继承自“星”类是有意义的。在这种情况下,我会选择一个静态实例,并使用静态getter方法。

为了澄清,这就是我所说的:

public class Star {
    private final String name;
    private final double density, massInKg;  

    public Star(String name, double density, double massInKg) {
        // ...
    }

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

public final class Sun extends Star {
    public static final Sun INSTANCE = new Sun();

    private Sun() { super("The shiniest of all", /**...**/, /**...**/); }
}

Sun 可以使用 Star 的所有方法并定义新的方法。这在枚举类型中是不可能的(我是指扩展类)。

如果没有必要建模此类继承关系,正如您所说,那么 enum 更适合,或者至少更容易和清晰。例如,如果一个应用程序每个 JVM 有一个单独的 ApplicationContext,将其作为单例对象是有意义的,通常不需要继承任何东西或可扩展。然后我会使用一个 enum

请注意,在某些语言(例如 Scala)中,有一个特殊的关键字用于单例(object),它不仅可以轻松地定义单例,而且完全替换了静态方法或字段的概念。


所以,即使它不能被继承,它仍然很有用,因为它可以自我继承,在大多数情况下非常方便。是的。这也是一个原因,除了线程安全之外。 - Gangnus
是的,作为一个将其功能表达为静态状态和静态方法的类,而不是通过对象引用访问单例,通过类名访问它。我相信你指的是一个静态实例,但问题是关于一个静态类的。 - RealSkeptic
嗯,我需要重新修改这个,我也误读了问题。我在考虑实现类似于Scala中的object关键字的东西。例如,枚举不能继承自抽象类,但是我所说的可以。尽管如此,似乎我误解了问题。 - Dici
@RealSkeptic 不是为了从枚举中继承,而是使用继承在其他类的基础上创建枚举。我们不能基于类字段/方法来实现它。是的,他没有直接说出来,但思路在那里(“从Star类继承它是有意义的”)。顺便说一句,没有人在答案中直接说出来。关于同步的可能性也是如此 :-( - Gangnus
@Gangnus 你不能基于另一个类构建枚举。你不能编写extends,因为它已经扩展了Enum类。 - RealSkeptic
显示剩余8条评论

0
  1. ENUM 单例模式很容易编写。与使用双重同步块实现懒汉式单例相比,它将占用非常少的代码,代码简洁优雅。

     public enum EasySingleton{
        INSTANCE;
    }
    
  2. ENUM 实例的创建是线程安全的。

  3. ENUM 单例可以自行处理序列化。

    传统的实现 Serializable 接口的单例不再是单例,因为 readObject() 方法总是像 Java 中的构造函数一样返回一个新实例。您可以通过使用 readResolve() 方法并且替换新创建的实例来避免这种情况。

    private Object readResolve(){
        return INSTANCE;
    }
    

看一下这个单例模式的文章


2
OP 正在尝试区分使用对象实现的单例类型和不公开对象而完全通过静态方法和字段实现的单例类型。 - Sotirios Delimanolis
如果你从一开始就创建枚举实例,那么它是线程安全的。但是在延迟初始化的情况下,你应该自己确保它的线程安全性。因此,它可以变得线程安全,但要注意,你需要自己付出注意力。 - Gangnus
我已经回答了OP的问题:在Mureinik的回答中,枚举单例的用途是什么,而不是类字段/方法。 - Ravindra babu
个人而言,我不喜欢单例的懒加载初始化。我们知道需要单例对象,我认为通过复杂的双重锁定来延迟构造没有任何优点。 - Ravindra babu
我能看到它 :-). 但是,与其使用“is”,最好使用“可以被制作”。这样你的第二点就更加精确了。 - Gangnus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接