单例设计模式:
保证一个类仅有一个对象,并提供一个访问它的全局访问点。
比较常见的实现方式是通过静态内部类实现。
public class Singleton{
private Singleton() {}
private static class SingletonInstance{
private static final Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonInstance.instance;
}
public void doSomething() {}
}
- 内部静态类不会自动初始化,只有调用静态内部类的方法,静态域,或者构造方法的时候才会加载静态内部类。保证了该类在未调用getInstance()之前不会因为初始化对象而浪费内存。
- static关键字修饰的变量被称为静态变量。静态变量随着类的加载而存在。静态变量不属于对象,属于类,所以不需要为内部类创建对象就可直接使用该变量。
关于静态变量,我记得18年在《网易公开课》上看了《麻省理工学院公开课:计算机科学及编程导论》的公开课,教授曾经说过这么一句话“Static semantics basically says which programs are meaningful.”
- final关键字保证了instance初始化之后就不可被改变。
但是该方式有个缺点:不提供序列化机制。
《effective java 3th》一书中还推荐了另一种实现方法:使用Enum类实现单例
public enum Singleton {
INSTANCE;
public void doSomeThing(){}
}
首先来看一下最简单的Enum类
public enum Color{
RED, GREEN, BLUE;
}
通过反编译之后得到以下源码(此处删掉了values()和valueOf(String s)方法)
public final class Color extends Enum
{
private Color(String s, int i)
{
super(s, i);
}
public static final Color RED;
public static final Color GREEN;
public static final Color BLUE;
private static final Color ENUM$VALUES[];
static
{
RED = new Color("RED", 0);
GREEN = new Color("GREEN", 1);
BLUE = new Color("BLUE", 2);
ENUM$VALUES = (new Color[] {
RED, GREEN, BLUE
});
}
}
由此可以看出,枚举类型Color是Enum类的一个子类,因为java只有单继承,所以该类被定义为final class。
枚举类型中定义的枚举变量是静态常量类型static final ,初始化在static{}域中进行。即每一个枚举类型的枚举变量在JVM中都是唯一的。只有在该枚举类型第一次被加载的时候,枚举变量才会被初始化,且初始化之后不可被更改。
还有一点尤为重要:使用Enum实现单例模式还无偿地提供了序列化机制,保证单例。
java的对象序列化将那些实现了Serializable接口的对象转换成一个字节序列,并能够在以后将这个字节序列完全恢复为原来的对象。
反序列化是通过调用readObject()方法实现的,但是该方法返回的是一个新创建出来的对象。
而Enum在序列化时仅仅将枚举变量的name属性输出到结果中,反序列化的时候是通过Enum中的valueOf()方法来根据name查找枚举对象。同时Enum禁用了readObject()方法,使其在被调用的时候抛出异常。这就保证了反序列化时不会返回一个新的对象。
Enum源码如下:
public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
private void readObject(ObjectInputStream in) throws IOException,
ClassNotFoundException {
throw new InvalidObjectException("can't deserialize enum");
}
private void readObjectNoData() throws ObjectStreamException {
throw new InvalidObjectException("can't deserialize enum");
}
}
总结:
在不考虑序列化的情况下,推荐使用静态内部类的方式实现单例模式。否则推荐使用Enum实现单例模式。