开发者社区> sea-boat> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

从JDK角度认识枚举enum

简介:
+关注继续查看

前言

对于比较稳定的值集合,Java 提供了枚举来定义,通过它可以很方便管理集合。那么 Java 的枚举是通过怎样的机制实现的?本文将从 JDK 角度来看看枚举的原理。

定义枚举

使用很简单,比如定义一个表示“环保”、“交通”、“手机”三个值的集合,那么就可以直接定义如下,然后可直接 Labels.ENVIRONMENT 使用,


public enum Labels {

  ENVIRONMENT(), TRAFFIC(), PHONE();

}

同时也可以使用带构造函数的枚举,如下,可以通过 getName 获取值。

public enum Labels0 {

  ENVIRONMENT("环保"), TRAFFIC("交通"), PHONE("手机");

  private String name;

  private Labels0(String name) {
    this.name = name;
  }

  public String getName() {
    return name;
  }

}

编译器做了什么

Java中的枚举的实现机制是怎样的?枚举看起来有点像上帝扔给我们的语法糖,秉着深入挖一挖的精神,看看枚举是相关实现,看看编译器做了什么。用 javap 看上面两个枚举编译后的字节码:

public final class com.seaboat.Labels extends java.lang.Enum<com.seaboat.Labels> {
  public static final com.seaboat.Labels ENVIRONMENT;
  public static final com.seaboat.Labels TRAFFIC;
  public static final com.seaboat.Labels PHONE;
  static {};
  public static com.seaboat.Labels[] values();
  public static com.seaboat.Labels valueOf(java.lang.String);
}
public final class com.seaboat.Labels0 extends java.lang.Enum<com.seaboat.Labels0> {
  public static final com.seaboat.Labels0 ENVIRONMENT;
  public static final com.seaboat.Labels0 TRAFFIC;
  public static final com.seaboat.Labels0 PHONE;
  static {};
  public java.lang.String getName();
  public static com.seaboat.Labels0[] values();
  public static com.seaboat.Labels0 valueOf(java.lang.String);
}

可以清晰地看到枚举被编译后其实就是一个类,该类被声明成 final,说明其不能被继承,同时它继承了 Enum 类。枚举里面的元素被声明成 static final ,另外生成一个静态代码块 static{},最后还会生成 values 和 valueOf 两个方法。下面以最简单的 Labels 为例,一个一个模块来看。

Enum 类

Enum 类是一个抽象类,主要有 name 和 ordinal 两个属性,分别用于表示枚举元素的名称和枚举元素的位置索引,而构造函数传入的两个变量刚好与之对应。

  • toString 方法直接返回 name。
  • equals 方法直接用 == 比较两个对象。
  • hashCode 方法调用的是父类的 hashCode 方法。
  • 枚举不支持 clone、finalize 和 readObject 方法。
  • compareTo 方法可以看到就是比较 ordinal 的大小。
  • valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素。
public abstract class Enum<E extends 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;
    }
    
    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) {
        Enum<?> other = (Enum<?>)o;
        Enum<E> self = this;
        if (self.getClass() != other.getClass() && 
            self.getDeclaringClass() != other.getDeclaringClass())
            throw new ClassCastException();
        return self.ordinal - other.ordinal;
    }

    @SuppressWarnings("unchecked")
    public final Class<E> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
    }

    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);
    }

    @SuppressWarnings("deprecation")
    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");
    }
}

静态代码块

可以看到静态代码块主要完成的工作就是先分别创建 Labels 对象,然后将“ENVIRONMENT”、“TRAFFIC”和“PHONE”字符串作为 name ,按照顺序分别分配位置索引0、1、2作为 ordinal,然后将其值设置给创建的三个 Labels 对象的 name 和 ordinal 属性,此外还会创建一个大小为3的 Labels 数组 ENUM$VALUES,将前面创建出来的 Labels 对象分别赋值给数组。

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #1                  // class com/seaboat/Labels
         3: dup
         4: ldc           #14                 // String ENVIRONMENT
         6: iconst_0
         7: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #19                 // Field ENVIRONMENT:Lcom/seaboat/Labels;
        13: new           #1                  // class com/seaboat/Labels
        16: dup
        17: ldc           #21                 // String TRAFFIC
        19: iconst_1
        20: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #22                 // Field TRAFFIC:Lcom/seaboat/Labels;
        26: new           #1                  // class com/seaboat/Labels
        29: dup
        30: ldc           #24                 // String PHONE
        32: iconst_2
        33: invokespecial #15                 // Method "<init>":(Ljava/lang/String;I)V
        36: putstatic     #25                 // Field PHONE:Lcom/seaboat/Labels;
        39: iconst_3
        40: anewarray     #1                  // class com/seaboat/Labels
        43: dup
        44: iconst_0
        45: getstatic     #19                 // Field ENVIRONMENT:Lcom/seaboat/Labels;
        48: aastore
        49: dup
        50: iconst_1
        51: getstatic     #22                 // Field TRAFFIC:Lcom/seaboat/Labels;
        54: aastore
        55: dup
        56: iconst_2
        57: getstatic     #25                 // Field PHONE:Lcom/seaboat/Labels;
        60: aastore
        61: putstatic     #27                 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
        64: return
      LineNumberTable:
        line 5: 0
        line 3: 39
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

values 方法

可以看到它是一个静态方法,主要是使用了前面静态代码块中的 Labels 数组 ENUM$VALUES,调用 System.arraycopy 对其进行复制,然后返回该数组。所以通过 Labels.values()[2]就能获取到数组中索引为2的元素。

  public static com.seaboat.Labels[] values();
    descriptor: ()[Lcom/seaboat/Labels;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=5, locals=3, args_size=0
         0: getstatic     #27                 // Field ENUM$VALUES:[Lcom/seaboat/Labels;
         3: dup
         4: astore_0
         5: iconst_0
         6: aload_0
         7: arraylength
         8: dup
         9: istore_1
        10: anewarray     #1                  // class com/seaboat/Labels
        13: dup
        14: astore_2
        15: iconst_0
        16: iload_1
        17: invokestatic  #35                 // Method java/lang/System.arraycopy:(Ljava/lang/Object;ILjava/lang/Object;II)V
        20: aload_2
        21: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

valueOf 方法

该方法同样是个静态方法,可以看到该方法的实现是间接调用了父类 Enum 类的 valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素,比如可以通过 Labels.valueOf("ENVIRONMENT")获取 Labels.ENVIRONMENT

public static com.seaboat.Labels valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lcom/seaboat/Labels;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #1                  // class com/seaboat/Labels
         2: aload_0
         3: invokestatic  #43                 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #1                  // class com/seaboat/Labels
         9: areturn
      LineNumberTable:
        line 1: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature

总结

枚举本质其实也是一个类,而且都会继承java.lang.Enum类,同时还会生成一个静态代码块 static{},并且还会生成 values 和 valueOf 两个方法。而上述的工作都需要由编译器来完成,然后我们就可以像使用我们熟悉的类那样去使用枚举了。

-------------推荐阅读------------

2017文章汇总——机器学习篇

2017文章汇总——Java及中间件

2017文章汇总——深度学习篇

2017文章汇总——JDK源码篇

------------------广告时间----------------

公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。

鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有需要的朋友可以购买。感谢各位朋友。

为什么写《Tomcat内核设计剖析》

欢迎关注:

这里写图片描述

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
Swift - Enum枚举 源码分析
Swift - Enum枚举 源码分析
21 0
开源项目推荐:C++枚举转字符串,magic_enum
开源项目推荐:C++枚举转字符串,magic_enum
154 0
视频会议玩消失?借助神器TensorFlow.js,200行代码教你“隐身”,GitHub标星3k+
视频会议玩消失?借助神器TensorFlow.js,200行代码教你“隐身”,GitHub标星3k+
67 0
JVM 异常表及 try-catch-finally 字节码分析
云栖号资讯:【点击查看更多行业资讯】在这里您可以找到不同行业的第一手的上云资讯,还在等什么,快来! 作为一个“有经验”的 Java 工程师,你一定知道什么是try-catch-finally代码块。
606 0
Bind Enum to Combobox.SelectedIndex
原文:Bind Enum to Combobox.SelectedIndex Do you mean that you want to bind a variable (not a property) to ComboBox.
777 0
+关注
sea-boat
擅长篮球、跑步、游泳、羽毛球、编程、看书、写书的顾家好男人! Java深度、大数据、中间件、搜索引擎、机器学习、深度学习、Python、C++、开源! 《Tomcat内核设计剖析》作者。
276
文章
0
问答
文章排行榜
最热
最新
相关电子书
更多
低代码开发师(初级)实战教程
立即下载
阿里巴巴DevOps 最佳实践手册
立即下载
冬季实战营第三期:MySQL数据库进阶实战
立即下载