我读过,在Java中使用枚举实现单例是可能的,例如:
public enum MySingleton {
INSTANCE;
}
但是,上面是如何工作的呢?具体来说,Object必须被实例化。这里,MySingleton是如何被实例化的?谁正在执行new MySingleton()?
我读过,在Java中使用枚举实现单例是可能的,例如:
public enum MySingleton {
INSTANCE;
}
但是,上面是如何工作的呢?具体来说,Object必须被实例化。这里,MySingleton是如何被实例化的?谁正在执行new MySingleton()?
当前回答
在Joshua Bloch的这本Java最佳实践书中,你可以找到解释为什么你应该用私有构造函数或Enum类型强制Singleton属性。这一章很长,所以要总结一下:
使一个类成为单例类会使测试它的客户端变得困难,因为不可能用一个模拟实现代替单例类,除非它实现了一个接口作为它的类型。 推荐的方法是通过简单地创建一个包含一个元素的枚举类型来实现singleton:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
这种方法在功能上等同于公共字段方法,只是它 更简洁,免费提供序列化机制,并提供一个 铁一般的保证,防止多重实例化,即使面对复杂 序列化或反射攻击。
尽管这种方法尚未得到广泛应用 采用单元素枚举类型是实现单元素的最佳方式。
其他回答
枚举类型是类的一种特殊类型。
你的枚举实际上会被编译成
public final class MySingleton {
public final static MySingleton INSTANCE = new MySingleton();
private MySingleton(){}
}
当您的代码第一次访问INSTANCE时,MySingleton类将由JVM加载并初始化。这个过程会初始化上面的静态字段一次(惰性地)。
这一点,
public enum MySingleton {
INSTANCE;
}
具有隐式空构造函数。让它显式,
public enum MySingleton {
INSTANCE;
private MySingleton() {
System.out.println("Here");
}
}
如果您随后使用main()方法添加另一个类,如
public static void main(String[] args) {
System.out.println(MySingleton.INSTANCE);
}
你会看到
Here
INSTANCE
Enum字段是编译时常量,但它们是其枚举类型的实例。并且,它们是在第一次引用枚举类型时构造的。
因为单例模式是关于拥有一个私有构造函数并调用一些方法来控制实例化(比如一些getInstance),所以在Enums中我们已经有了一个隐式私有构造函数。
我不知道JVM或一些容器是如何控制我们的枚举实例的,但它似乎已经使用了隐式的单例模式,不同的是我们不调用getInstance,我们只是调用Enum。
在Joshua Bloch的这本Java最佳实践书中,你可以找到解释为什么你应该用私有构造函数或Enum类型强制Singleton属性。这一章很长,所以要总结一下:
使一个类成为单例类会使测试它的客户端变得困难,因为不可能用一个模拟实现代替单例类,除非它实现了一个接口作为它的类型。 推荐的方法是通过简单地创建一个包含一个元素的枚举类型来实现singleton:
// Enum singleton - the preferred approach
public enum Elvis {
INSTANCE;
public void leaveTheBuilding() { ... }
}
这种方法在功能上等同于公共字段方法,只是它 更简洁,免费提供序列化机制,并提供一个 铁一般的保证,防止多重实例化,即使面对复杂 序列化或反射攻击。
尽管这种方法尚未得到广泛应用 采用单元素枚举类型是实现单元素的最佳方式。
在某种程度上,前面已经提到,枚举是具有特殊条件的java类,其定义必须以至少一个“enum常量”开始。
除此之外,枚举不能被扩展或用于扩展其他类,枚举是一个像任何类一样的类,你可以通过在常量定义下面添加方法来使用它:
public enum MySingleton {
INSTANCE;
public void doSomething() { ... }
public synchronized String getSomething() { return something; }
private String something;
}
你可以通过下面这些行访问单例对象的方法:
MySingleton.INSTANCE.doSomething();
String something = MySingleton.INSTANCE.getSomething();
枚举的使用,而不是类,正如在其他回答中提到的,主要是关于单例的线程安全实例化,并保证它总是只有一个副本。
也许,最重要的是,这种行为是由JVM本身和Java规范保证的。
下面是Java规范中关于如何防止枚举实例的多个实例的部分:
An enum type has no instances other than those defined by its enum constants. It is a compile-time error to attempt to explicitly instantiate an enum type. The final clone method in Enum ensures that enum constants can never be cloned, and the special treatment by the serialization mechanism ensures that duplicate instances are never created as a result of deserialization. Reflective instantiation of enum types is prohibited. Together, these four things ensure that no instances of an enum type exist beyond those defined by the enum constants.
值得注意的是,在实例化之后,任何线程安全问题都必须像在任何其他类中使用synchronized关键字等一样处理。