博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
从JDK角度认识枚举enum
阅读量:7207 次
发布时间:2019-06-29

本文共 7948 字,大约阅读时间需要 26 分钟。

前言

对于比较稳定的值集合,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
{ 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
{ 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
> implements Comparable
, 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
self = this; if (self.getClass() != other.getClass() && self.getDeclaringClass() != other.getDeclaringClass()) throw new ClassCastException(); return self.ordinal - other.ordinal; } @SuppressWarnings("unchecked") public final Class
getDeclaringClass() { Class
clazz = getClass(); Class
zuper = clazz.getSuperclass(); return (zuper == Enum.class) ? (Class
)clazz : (Class
)zuper; } public static
> T valueOf(Class
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 "
":(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 "
":(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 "
":(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 两个方法。而上述的工作都需要由编译器来完成,然后我们就可以像使用我们熟悉的类那样去使用枚举了。

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

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

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

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

欢迎关注:

转载地址:http://xulum.baihongyu.com/

你可能感兴趣的文章
切换带空格的目录下
查看>>
Nginx 在ubuntu14.04下的安装
查看>>
51nod 1100:斜率最大
查看>>
简单工厂模式(C++)
查看>>
关于Java中的Arrays.copyOfRange()方法
查看>>
正确地黑C
查看>>
一个程序员的自白(三十而立)
查看>>
生产者、消费者、队列
查看>>
关于java中的==,equal,new,= 的一些相关知识(有点乱)
查看>>
一种NVMe SSD友好的数据存储系统设计
查看>>
IT168采访记录
查看>>
oracle删除一个用户
查看>>
老男孩教育学员参观机房实践活动
查看>>
《企业云桌面实施》-小技巧-08-建筑设计行业-真实效果-漫游动画-三维视图渲染...
查看>>
SUSE LINUX系统文件句柄限制的修改
查看>>
贺双节,签名版限量特惠
查看>>
警惕“***性社工”现象
查看>>
Exchange 2013与OWA13集成
查看>>
有话请直说
查看>>
虚机不能启动的特例思考
查看>>