从 JVM 角度看「枚举」本质

首先我们需要介绍什么是「枚举」,「枚举」在 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;
}

                                                             能看到这里的都是真爱了,长按二维码关注

                                                               一起在知识的海洋里狗刨,一起学习成长

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值