对于比较稳定的值集合,Java 提供了枚举来定义,经过它能够很方便管理集合。那么 Java 的枚举是经过怎样的机制实现的?本文将从 JDK 角度来看看枚举的原理。java
使用很简单,好比定义一个表示“环保”、“交通”、“手机”三个值的集合,那么就能够直接定义以下,而后可直接 Labels.ENVIRONMENT 使用,数组
public enum Labels {
ENVIRONMENT(), TRAFFIC(), PHONE();
}
复制代码
同时也可使用带构造函数的枚举,以下,能够经过 getName 获取值。bash
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 类是一个抽象类,主要有 name 和 ordinal 两个属性,分别用于表示枚举元素的名称和枚举元素的位置索引,而构造函数传入的两个变量恰好与之对应。分布式
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、一、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
复制代码
能够看到它是一个静态方法,主要是使用了前面静态代码块中的 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
复制代码
该方法一样是个静态方法,能够看到该方法的实现是间接调用了父类 Enum 类的 valueOf 方法,根据传入的字符串 name 来返回对应的枚举元素,好比能够经过 Labels.valueOf("ENVIRONMENT")
获取 Labels.ENVIRONMENT
。ui
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 两个方法。而上述的工做都须要由编译器来完成,而后咱们就能够像使用咱们熟悉的类那样去使用枚举了。this
-------------推荐阅读------------
------------------广告时间----------------
公众号的菜单已分为“分布式”、“机器学习”、“深度学习”、“NLP”、“Java深度”、“Java并发核心”、“JDK源码”、“Tomcat内核”等,可能有一款适合你的胃口。
鄙人的新书《Tomcat内核设计剖析》已经在京东销售了,有须要的朋友能够购买。感谢各位朋友。
欢迎关注: