从JDK角度认识枚举enum

栏目: Java · 发布时间: 8年前

内容简介:从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内核设计剖析》

欢迎关注:

从JDK角度认识枚举enum


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

创业时, 我们在知乎聊什么?

创业时, 我们在知乎聊什么?

知乎 / 中信出版社 / 2014-1 / 42.00元

★前所未有的互联网出版实验,500万知友亲手甄选内容,知乎三年创业问答精华大集结 ★史上最真诚创业书,用互联网思维讲透创业的逻辑 ★在知乎,最强大互联网创业群体真实分享创业路上的荣耀与隐忧 ★从Idea到步入正轨,创业公司如何招人、做技术、卖产品、找融资、建团队、处理法务? 他们在知乎聊创业: 创新工场创始人李开复 天使投资人 徐小平 小米科技创始人 雷军......一起来看看 《创业时, 我们在知乎聊什么?》 这本书的介绍吧!

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具