首先我们需要介绍什么是「枚举」,「枚举」在 Java 是中一种特殊的数据类型,它允许变量是一组预定义常量。因为是常量,「枚举」类型的字段的名称是大写字母。在 Java 编程语言中,使用 enum 关键字定义 enum 类型。在任何需要表示一组固定常量的时候,都应该使用枚举类型,比如我们需要表示周一到周日,就可以使用下面的「枚举」类型表示。
public enum Day {
SUNDAY, MONDAY, TUESDAY, WEDNESDAY,
THURSDAY, FRIDAY, SATURDAY
}
Java 编程语言中「枚举」声明定义了一个类(称为「枚举」类型)。enum 类主体可以包括方法和其他字段。JVM 编译器在创建枚举时自动添加一些特殊的方法。我们通过下面的代码,看一下 JVM 编译器对我们的「枚举」做了哪些处理。
public enum Enumerate {
INSTANCE;
}
上面我定义一个简单的「枚举」类型 Enumerate ,里面只有一个常量 INSTANCE ,我们通过 CMD 命令提示符使用 javac 命令(javac 全称 Java compiler ,Java 语言编程编译器)将上面的 Java 代码编译成 class 字节码文件。然后我们可以通过 javap 命令查看 Java 编译器为我们生成的字节码(javap 是 JDK 自带的反汇编器),javap 的用法,各个参数含义,大家可以在自己电脑上通过CMD 命令提示符窗口输入 javap -help 查看(前提:电脑安装并配置好 JDK)。
Compiled from "Enumerate.java"
public final class Enumerate extends java.lang.Enum<Enumerate> {
public static final Enumerate INSTANCE;
private static final Enumerate[] $VALUES;
public static Enumerate[] values();
public static Enumerate valueOf(java.lang.String);
private Enumerate();
static {};
}
上面是 javap 反编译 Enumerate.class 文件,得到 JVM 实际处理我们定义的 Enumerate「枚举」代码,然后我们一起看一下, JVM 对我们定义的「枚举」做了哪些处理,通过这些处理可以看一下「枚举」类型应该具有那些特性。
- 让我们定义的 Enumerate 继承 java.lang.Enum 类, java.lang.Enum 类接收 Enumerate 泛型。使用了 final 修饰了 Enumerate 类。
通过这个定义,我们知道「枚举」类型的本质也是一个类。「枚举」类型不允许继承其他类,因为 Java 规定类是单继承,而「 枚举」类型已经继承了 Enum 类。「枚举」类型不能被其他类继承,因为「 枚举」类型是用 final 关键字修饰的。 - 我们在 Java 代码中定义的 INSTANCE 「枚举」,被定义成了 Enumerate 类的实例对象,并且这个实例对象是用 public static final 修饰的。
我们定义的「枚举」常量是当前「枚举」类型的实例对象,所以他可以调用当前「 枚举」类型中的方法和变量,并且这个实例对象是静态不可变的。 - 定义了静态、私有的 $VALUES 数组,用来存储我们定义的「枚举」。
- 定义了静态的 values() 方法,返回当前「枚举」类型中所有的「枚举」常量。
- 定义了静态的 valueOf 方法,返回指定「枚举」类型的「枚举」常量。
- 定义了私有的构造方法。
「 枚举」类型不能被实例化。
- 定义了 static 静态代码块,用于初始化「枚举」常量和「枚举」常量数组。
我们通过反编译发现我们定义的「枚举」类型继承了 Enum 类,那我们一起来看一下 Enum 类的源码(JDK 1.8)。
//Enum类所有Java语言枚举类型的公共基类
public abstract class Enum<E extends java.lang.Enum<E>>
implements Comparable<E>, Serializable
//「枚举」常量的名字
private final String name
//「枚举」常量声明时的顺序
private final int ordinal;
// 获取「枚举」常量 名字
public final String name() {
return name;
}
// 获取「枚举」常量 顺序
public final int ordinal() {
return ordinal;
}
// 构造方法,给「枚举」常量名字和顺序初始化
protected Enum(String name, int ordinal) {
this.name = name;
this.ordinal = ordinal;
}
// 直接返回「枚举」常量名称,获取常量名称推荐使用这个方法,而不是使用 name 方法获取「枚举」常量名称
public String toString() {
return name;
}
// 直接比较内存地址
public final boolean equals(Object other) {
return this == other;
}
public final int hashCode() {
return super.hashCode();
}
// 不允许克隆,直接抛出异常
protected final Object clone() throws CloneNotSupportedException {
throw new CloneNotSupportedException();
}
// 用来比较 「枚举」常量 的顺序
public final int compareTo(E o) {
java.lang.Enum other = (java.lang.Enum) o;
java.lang.Enum self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
public final Class<E> getDeclaringClass() {
Class clazz = getClass();
Class zuper = clazz.getSuperclass();
return (zuper == java.lang.Enum.class) ? clazz : zuper;
}
// 根据「枚举」类型和「枚举」常量名称返回对应的「枚举」常量
public static <T extends java.lang.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);
}
protected final void finalize() {
}
// 不允许反序列化,直接抛出异常
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");
}
}
了解完什么是「枚举」和 Java 语言枚举类型的公共基类 Enum 类的源码后,我们来看看,为什么说「枚举」是单例模式最简单的实现方式。
单例设计模式(Singleton Design Pattern):一个类只允许创建一个对象(或者叫实例),那这个类就是一个单例类,这种设计模式就叫作单例设计模式,简称单例模式。
通过上面单例模式的定义,我们知道实现一个单例,应该着重关注下面两点:
- 构造函数需要是 private 访问权限,防止外部通过 new 关键字来创建对象实例。
- 创建对象时多线程的安全。
这两条规则不是刚刚好满足我们之前介绍的「枚举」类型的特征吗,「枚举」类型的构造函数被编译器定义为 private ,通过 Enum 类源码可以看到,clone 和反序列化直接抛出异常,保证了每个实例对象的唯一性。对象实例都是由 JVM 来负责创建,保证了线程的安全。下面的代码便是使用「枚举」实现了一个单例,相比于用懒汉、饿汉、双重检测、静态内部类等方式实现单例,简直简单太多了,感觉「枚举」就是 JVM 提供给我们用来实现单例模式的一个语法糖。
public enum Enumerate {
INSTANCE;
}
能看到这里的都是真爱了,长按二维码关注
一起在知识的海洋里狗刨,一起学习成长