相关文章推荐
八块腹肌的小蝌蚪  ·  2015年计算机科学与技术学科系列学术报告之四·  8 月前    · 
飞奔的灯泡  ·  30年前垮坝惨剧河南水坝垮塌2.6万人遇难_ ...·  1 年前    · 
讲道义的橡皮擦  ·  《小鱼(双性强制NP)》现代 / 美人受 ...·  1 年前    · 
心软的黑框眼镜  ·  黑龙江:开展大型融媒体直播 ...·  2 年前    · 
豪爽的西装  ·  广东省人民政府办公厅关于印发农业农村部 ...·  2 年前    · 
Code  ›  java 枚举(enum) 全面解读开发者社区
string javascript enum 枚举类型
https://cloud.tencent.com/developer/article/2035041
一身肌肉的莲藕
1 年前
全栈程序员站长

java 枚举(enum) 全面解读

前往小程序,Get 更优 阅读体验!
立即前往
腾讯云
开发者社区
文档 建议反馈 控制台
首页
学习
活动
专区
圈层
工具
文章/答案/技术大牛
发布
首页
学习
活动
专区
圈层
工具
返回腾讯云官网
全栈程序员站长
首页
学习
活动
专区
圈层
工具
返回腾讯云官网
社区首页 > 专栏 > java 枚举(enum) 全面解读

java 枚举(enum) 全面解读

作者头像
全栈程序员站长
发布 于 2022-06-29 19:19:09
发布 于 2022-06-29 19:19:09
1K 0 0
代码可运行
举报
文章被收录于专栏: 全栈程序员必看 全栈程序员必看
运行总次数: 0
代码可运行

简介

枚举是Java1.5引入的新特性,通过关键字enum来定义枚举类。枚举类是一种特殊类,它和普通类一样可以使用构造器、定义成员变量和方法,也能实现一个或多个接口,但枚举类不能继承其他类.

原理分析

枚举类型使用的最常用类型就是枚举常量.下面通过一个简单的Demo来说明枚举的原理.

代码语言: javascript
代码 运行次数: 0
运行
复制
// 定义
public enum Color {
    BLACK, WHITE
// 使用
public class Main {
    public static void main(String[] args) {
        System.out.println(Color.BLACK);
// 结果
// BLACK

这样只是能够知道枚举简单的使用方法,不能看出枚举的特点和枚举的具体实现. 下面我们通过 jad 工具来反编译 Color 类, 通过 jad -sjava Color.class 反编译出一份java文件.

代码语言: javascript
代码 运行次数: 0
运行
复制
// final修饰,无法被继承
public final class Color extends Enum {
    // 为了避免 返回的数组修改,而引起内部values值的改变,返回的是原数组的副本
    public static Color[] values() {
        return (Color[]) $VALUES.clone();
    // 按名字获取枚举实例
    public static Color valueOf(String name) {
        return (Color) Enum.valueOf(em / Color, name);
    // 私有的构造函数
    private Color(String name, int ordinal) {
        super(name, ordinal);
    // enum第一行的声明的变量,都对应一个枚举实例对象
    public static final Color BLACK;
    public static final Color WHITE;
    private static final Color $VALUES[];
    // 静态域初始化,说明在类加载的cinit阶段就会被实例化,jvm能够保证类加载过程的线程安全
    static {
        BLACK = new Color("BLACK", 0);
        WHITE = new Color("WHITE", 1);
        $VALUES = (new Color[]{
                BLACK, WHITE
}

从反编译的类中,可以看出, 我们使用 enum 关键字编写的类,在编译阶段编译器会自动帮我们生成一份真正在jvm中运行的代码.

该类继承自 Enum 类, public abstract class Enum<E extends Enum<E>>implements Comparable<E>, Serializable .

Enum类接受一个继承自Enum的泛型.( 在反编译java文件中没有体现泛型是因为,泛型在阶段就会被类型类型擦除,替换为具体的实现. ).

从反编译的 Color 类中可以看出,在 enum 关键字的类中,第一行 (准确的说是第一个分号前) 定义的变量,都会生成一个 Color 实例,且它是在静态域中进行初始化的, 而静态域在类加载阶段的 cinit 中进行初始化,所以枚举对象是线程安全的,由JVM来保证.

生成的枚举类有 Color $VALUES[]; 成员变量,外部可以通过 values() 方法获取当前枚举类的所有实例对象.

Enum成员变量和方法分析

Enum类实现了 Comparable 接口,表明它是支持排序的,可以通过 Collections.sort 进行自动排序.实现了 public final int compareTo(E o) 接口,方法定义为 final 且其实现依赖的 ordinal 字段也是final类型,说明他只能根据 ordinal 排序,排序规则不可变.

代码语言: javascript
代码 运行次数: 0
运行
复制
    public final int compareTo(E o) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && // optimization
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

ordinal: 表示枚举的顺序,从 Color 类中可以看出,它是从0开始按自然数顺序增长,且其值是final类型,外部无法更改.对于 ordinal() 方法,官方建议尽量不要使用它,它主要是提供给 EnumMap , EnumSet 使用的.

代码语言: javascript
代码 运行次数: 0
运行
复制
/**
     * Returns the ordinal of this enumeration constant (its position
     * in its enum declaration, where the initial constant is assigned
     * an ordinal of zero).
     * Most programmers will have no use for this method.  It is
     * designed for use by sophisticated enum-based data structures, such
     * as {@link java.util.EnumSet} and {@link java.util.EnumMap}.
     * @return the ordinal of this enumeration constant
    public final int ordinal() {
        return ordinal;
    }

name: 表示枚举类的名字,从 Color 类的构造函数可以看出,它的值就是我们定义的实例的名称. 我们在例子中之所以能打印出实例名称,是因为 它的 toString() 方法直接返回了name属性.

代码语言: javascript
代码 运行次数: 0
运行
复制
/**
     * Returns the name of this enum constant, as contained in the
     * declaration.  This method may be overridden, though it typically
     * isn't necessary or desirable.  An enum type should override this
     * method when a more "programmer-friendly" string form exists.
     * @return the name of this enum constant
    public String toString() {
        return name;
    }

equals(): 从其实现来看, 我们程序中使用 == 或者 equals 来判断两个枚举相等都是一样的.

代码语言: javascript
代码 运行次数: 0
运行
复制
 public final boolean equals(Object other) {
        return this==other;
    }

getDeclaringClass(): 方法返回枚举声明的Class对象

每一个枚举类型极其定义的枚举变量在JVM中都是唯一的

这句话的意思是枚举类型它拥有的实例在编写的时候,就已经确定下,不能通过其他手段进行创建,且枚举变量在jvm有且只有一个对应的实例.

为了达到这个效果,它通过以下方法来确保.

  1. 类加载时创建,保证线程安全

从 Color 类中可以看出, Color对象是在静态域创建,由类加载时初始化,JVM保证线程安全,这样就能确保Color对象不会因为并发同时请求而错误的创建多个实例.

  1. 对序列化进行特殊处理,防止反序列化时创建新的对象

我们知道一旦实现了Serializable接口之后,反序列化时每次调用 readObject()方法返回的都是一个新创建出来的对象.

而枚举则不同,在序列化的时候Java仅仅是将枚举对象的name属性输出到结果中,反序列化的时候则是通过Enum的 valueOf() 方法来根据名字查找枚举对象。同时,编译器是不允许任何对这种序列化进行定制,因此禁用了 writeObject 、 readObject 、 readObjectNoData 、 writeReplace 和 readResolve 等方法。

代码语言: javascript
代码 运行次数: 0
运行
复制
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);
     * prevent default deserialization
    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");
    }
  1. 私有构造函数, 无法正常的 new 出对象
代码语言: javascript
代码 运行次数: 0
运行
复制
    // 私有的构造函数
    private Color(String name, int ordinal) {
        super(name, ordinal);
    }
  1. 无法通过 clone() 方法,克隆对象
代码语言: javascript
代码 运行次数: 0
运行
复制
    /**
     * Throws CloneNotSupportedException.  This guarantees that enums
     * are never cloned, which is necessary to preserve their "singleton"
     * status.
    protected final Object clone() throws CloneNotSupportedException {
        throw new CloneNotSupportedException();
    }
  1. 无法通过反射的方式创建枚举对象

枚举类型,在 JVM 层面禁止了通过反射构造枚举实例的行为,如果尝试通过反射创建,将会报 Cannot reflectively create enum objects .

代码语言: javascript
代码 运行次数: 0
运行
复制
    static void reflectTest() throws Exception {
        // 获取类对象
        Class<?> cls = Class.forName("em.Color");
        // 获取 color 的构造函数
        Constructor<?> constructor = cls.getDeclaredConstructor(String.class, int.class);
        // 获取访问权限
        constructor.setAccessible(true);
        // 实例化
        Object reflectColor = constructor.newInstance("name", 0);
    // 报错
    Exception in thread "main" java.lang.IllegalArgumentException: Cannot reflectively create enum objects
    at java.lang.reflect.Constructor.newInstance(Constructor.java:417)
    at Main.reflect(Main.java:24)
    at Main.main(Main.java:13)

枚举类的特点总结

  1. 枚举实例必须在 enum 关键字声明的类中显式的指定(首行开始的以第一个分号结束)
  2. 除了1, 没有任何方式(new,clone,反射,序列化)可以手动创建枚举实例
  3. 枚举类不可被继承
  4. 枚举类是线程安全的
  5. 枚举类型是类型安全的(typesafe)
  6. 无法继承其他类(已经默认继承Enum)

枚举的使用

  • 枚举常量

如上诉 Color 枚举类,就是典型的枚举常量.

它可以在 switch 语句中使用

代码语言: javascript
代码 运行次数: 0
运行
复制
    void enmuTest() {
        Color tag = Color.BLACK;
        switch (tag) {
            case WHITE:
                break;
            case BLACK:
                break;
    }

枚举类型是类型安全的,可以对传入的值进行类型检查:

如有个 handleColor(Color color) 方法,那么方法参数自动会对类型进行检查,只能传入 Color.WHITE 和 Color.BLACK ,如果使用 static final 定义的常量则不具备 类型安全的特点.

  • 枚举与构造函数

枚举类可以编写自己的构造函数,但是不能声明 public,protected ,为了是不让外部创建实例对象,默认为 private 且只能为它.

  • 枚举与类

除了枚举常量外, enum是一个完整的类,它也可以编写自己的构造方法以及方法,甚至实现接口.

这里需要注意,枚举类不能继承其他类,因为在编译时它已经继承了 Enum ,java无法多继承

代码语言: javascript
代码 运行次数: 0
运行
复制
// 实现Runnable接口,在这个类中没有意义,只是为了举例
public enum Color implements Runnable {
    WHITE("黑色"),
    BLACK("白色");
    private final String value;
    // 自定义构造,虽然没有写private,但是默认就是private
    Color(String v) {
        value = v;
    // 自定义方法
    public void draw() {
        System.out.println("绘制 " + value);
    // 重写方法
    @Override
    public String toString() {
        return value;
    // 实现接口方法
    @Override
    public void run() {
        // todo ...
}

枚举与单例模式

单例模式网上有6-7中写法,除了 枚举方式外, 都有两个致命的缺点, 不能完全保证单例在jvm中保持唯一性.

  1. 反射创建单例对象

解决方案 : 在构造上述中判断,当多于一个实例时,再调用构造函数,直接报错.

  1. 反序列化时创建对象

解决方案 : 使用readResolve()方法来避免此事发生.

这两种缺点虽然都有方式解决,但是不免有些繁琐.

枚举类天生有这些特性.而且实现单例相当简单.

代码语言: javascript
代码 运行次数: 0
运行
复制
public enum Singleton {
    INSTANCE;
    public void method() {
        // todo ...
}

所以,枚举实现的单例,可以说是最完美和简洁的单例了.推荐大家使用这种方式创建单例.

但是,枚举类的装载和初始化时会有时间和空间的成本. 它的实现比其他方式需要更多的内存空间,所以在Android这种受资源约束的设备中尽量避免使用枚举单例,而选择 双重检查锁(DCL) 和 静态内部类 的方式实现单例.

枚举与策略模式

特定的常量类型与主体中的方法或行为有关时,即当数据与行为之间有关联时,可以考虑使用枚举来实现策略模式.

如我们需要实现加减运算,就可以在枚举类型中声明一个 apply 抽象方法,在特定于常量的方法(Constant-specific class body的Constant -specific method implementation)中,用具体实现抽象方法.

代码语言: javascript
代码 运行次数: 0
运行
复制
public enum Operation {
    PLUS {
        // 实例中实现抽象方法
        public double apply(double x, double y) {
            return x + y;
    MINUS {
        public double apply(double x, double y) {
            return x - y;
    // 声明抽象方法
    public abstract double apply(double x, double y);
 double result = Operation.PLUS.apply(1, 2);

枚举与Android

在旧版的Android开发者官网的指南 Managing Your App’s Memory ,新版中已经被移除.

有这么一句话 :

Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.

 
推荐文章
八块腹肌的小蝌蚪  ·  2015年计算机科学与技术学科系列学术报告之四
8 月前
飞奔的灯泡  ·  30年前垮坝惨剧河南水坝垮塌2.6万人遇难_手机新浪网
1 年前
讲道义的橡皮擦  ·  《小鱼(双性强制NP)》现代 / 美人受 / 高H【双性不生子】_哔哩哔哩_bilibili
1 年前
心软的黑框眼镜  ·  黑龙江:开展大型融媒体直播 在线观看人数超71万-中国气象局政府门户网站
2 年前
豪爽的西装  ·  广东省人民政府办公厅关于印发农业农村部 广东省人民政府共同推进广东乡村振兴战略实施2022年度工作要点的通知  广东省人民政府门户网站
2 年前
今天看啥   ·   Py中国   ·   codingpro   ·   小百科   ·   link之家   ·   卧龙AI搜索
删除内容请联系邮箱 2879853325@qq.com
Code - 代码工具平台
© 2024 ~ 沪ICP备11025650号