前提

上一篇文章复习介绍了JDK中注解的底层实现,跟注解一样比较常用,但是底层实现比较神秘的还有枚举类型。趁着国庆假期的最后两天,把JDK中枚举的底层实现也进行一次探究。

通过例子查找本质

在探究JDK注解的底层实现的时候,因为预先参考了不少资料,所以整个过程有点"未卜先知"的意味,这里尝试用未知的角度去看注解的底层实现。先定义一个手机操作系统类型枚举PhoneOsEnum:

package club.throwable.enumeration;

public enum PhoneOsEnum {

	/**
* 安卓
*/
ANDROID(1, "android"), /**
* ios
*/
IOS(2, "ios"); private final Integer type;
private final String typeName; PhoneOsEnum(Integer type, String typeName) {
this.type = type;
this.typeName = typeName;
} public Integer getType() {
return type;
} public String getTypeName() {
return typeName;
}
}

这是一个很简单的枚举,接着使用JDK的反编译工具反编译出其字节码,执行下面的命令:

javap -c -v D:\Projects\rxjava-seed\target\classes\club\throwable\enumeration\PhoneOsEnum.class

然后就得到了关于PhoneOsEnum.class的很长的字节码,这里全部贴出来:

Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/enumeration/PhoneOsEnum.class
Last modified 2018-10-6; size 1561 bytes
MD5 checksum 6d3186042f54233219000927a2f196aa
Compiled from "PhoneOsEnum.java"
public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>
minor version: 0
major version: 52
flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
#1 = Fieldref #4.#49 // club/throwable/enumeration/PhoneOsEnum.$VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
#2 = Methodref #50.#51 // "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
#3 = Class #26 // "[Lclub/throwable/enumeration/PhoneOsEnum;"
#4 = Class #52 // club/throwable/enumeration/PhoneOsEnum
#5 = Methodref #17.#53 // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#6 = Methodref #17.#54 // java/lang/Enum."<init>":(Ljava/lang/String;I)V
#7 = Fieldref #4.#55 // club/throwable/enumeration/PhoneOsEnum.type:Ljava/lang/Integer;
#8 = Fieldref #4.#56 // club/throwable/enumeration/PhoneOsEnum.typeName:Ljava/lang/String;
#9 = String #18 // ANDROID
#10 = Methodref #57.#58 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#11 = String #59 // android
#12 = Methodref #4.#60 // club/throwable/enumeration/PhoneOsEnum."<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
#13 = Fieldref #4.#61 // club/throwable/enumeration/PhoneOsEnum.ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
#14 = String #20 // IOS
#15 = String #62 // ios
#16 = Fieldref #4.#63 // club/throwable/enumeration/PhoneOsEnum.IOS:Lclub/throwable/enumeration/PhoneOsEnum;
#17 = Class #64 // java/lang/Enum
#18 = Utf8 ANDROID
#19 = Utf8 Lclub/throwable/enumeration/PhoneOsEnum;
#20 = Utf8 IOS
#21 = Utf8 type
#22 = Utf8 Ljava/lang/Integer;
#23 = Utf8 typeName
#24 = Utf8 Ljava/lang/String;
#25 = Utf8 $VALUES
#26 = Utf8 [Lclub/throwable/enumeration/PhoneOsEnum;
#27 = Utf8 values
#28 = Utf8 ()[Lclub/throwable/enumeration/PhoneOsEnum;
#29 = Utf8 Code
#30 = Utf8 LineNumberTable
#31 = Utf8 valueOf
#32 = Utf8 (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
#33 = Utf8 LocalVariableTable
#34 = Utf8 name
#35 = Utf8 <init>
#36 = Utf8 (Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
#37 = Utf8 this
#38 = Utf8 Signature
#39 = Utf8 (Ljava/lang/Integer;Ljava/lang/String;)V
#40 = Utf8 getType
#41 = Utf8 ()Ljava/lang/Integer;
#42 = Utf8 getTypeName
#43 = Utf8 ()Ljava/lang/String;
#44 = Utf8 <clinit>
#45 = Utf8 ()V
#46 = Utf8 Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
#47 = Utf8 SourceFile
#48 = Utf8 PhoneOsEnum.java
#49 = NameAndType #25:#26 // $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
#50 = Class #26 // "[Lclub/throwable/enumeration/PhoneOsEnum;"
#51 = NameAndType #65:#66 // clone:()Ljava/lang/Object;
#52 = Utf8 club/throwable/enumeration/PhoneOsEnum
#53 = NameAndType #31:#67 // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#54 = NameAndType #35:#68 // "<init>":(Ljava/lang/String;I)V
#55 = NameAndType #21:#22 // type:Ljava/lang/Integer;
#56 = NameAndType #23:#24 // typeName:Ljava/lang/String;
#57 = Class #69 // java/lang/Integer
#58 = NameAndType #31:#70 // valueOf:(I)Ljava/lang/Integer;
#59 = Utf8 android
#60 = NameAndType #35:#36 // "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
#61 = NameAndType #18:#19 // ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
#62 = Utf8 ios
#63 = NameAndType #20:#19 // IOS:Lclub/throwable/enumeration/PhoneOsEnum;
#64 = Utf8 java/lang/Enum
#65 = Utf8 clone
#66 = Utf8 ()Ljava/lang/Object;
#67 = Utf8 (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
#68 = Utf8 (Ljava/lang/String;I)V
#69 = Utf8 java/lang/Integer
#70 = Utf8 (I)Ljava/lang/Integer;
{
public static final club.throwable.enumeration.PhoneOsEnum ANDROID;
descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final club.throwable.enumeration.PhoneOsEnum IOS;
descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static club.throwable.enumeration.PhoneOsEnum[] values();
descriptor: ()[Lclub/throwable/enumeration/PhoneOsEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=1, locals=0, args_size=0
0: getstatic #1 // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
3: invokevirtual #2 // Method "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
6: checkcast #3 // class "[Lclub/throwable/enumeration/PhoneOsEnum;"
9: areturn
LineNumberTable:
line 9: 0 public static club.throwable.enumeration.PhoneOsEnum valueOf(java.lang.String);
descriptor: (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
flags: ACC_PUBLIC, ACC_STATIC
Code:
stack=2, locals=1, args_size=1
0: ldc #4 // class club/throwable/enumeration/PhoneOsEnum
2: aload_0
3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
6: checkcast #4 // class club/throwable/enumeration/PhoneOsEnum
9: areturn
LineNumberTable:
line 9: 0
LocalVariableTable:
Start Length Slot Name Signature
0 10 0 name Ljava/lang/String; public java.lang.Integer getType();
descriptor: ()Ljava/lang/Integer;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #7 // Field type:Ljava/lang/Integer;
4: areturn
LineNumberTable:
line 31: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lclub/throwable/enumeration/PhoneOsEnum; public java.lang.String getTypeName();
descriptor: ()Ljava/lang/String;
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #8 // Field typeName:Ljava/lang/String;
4: areturn
LineNumberTable:
line 35: 0
LocalVariableTable:
Start Length Slot Name Signature
0 5 0 this Lclub/throwable/enumeration/PhoneOsEnum; static {};
descriptor: ()V
flags: ACC_STATIC
Code:
stack=6, locals=0, args_size=0
0: new #4 // class club/throwable/enumeration/PhoneOsEnum
3: dup
4: ldc #9 // String ANDROID
6: iconst_0
7: iconst_1
8: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: ldc #11 // String android
13: invokespecial #12 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
16: putstatic #13 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
19: new #4 // class club/throwable/enumeration/PhoneOsEnum
22: dup
23: ldc #14 // String IOS
25: iconst_1
26: iconst_2
27: invokestatic #10 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
30: ldc #15 // String ios
32: invokespecial #12 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
35: putstatic #16 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
38: iconst_2
39: anewarray #4 // class club/throwable/enumeration/PhoneOsEnum
42: dup
43: iconst_0
44: getstatic #13 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
47: aastore
48: dup
49: iconst_1
50: getstatic #16 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
53: aastore
54: putstatic #1 // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
57: return
LineNumberTable:
line 14: 0
line 19: 19
line 9: 38
}
Signature: #46 // Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
SourceFile: "PhoneOsEnum.java"

先看类的签名是public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>,它的父类是java.lang.Enum,父类的泛型就是自身club.throwable.enumeration.PhoneOsEnum。上面的字节码的可读性相对比较低,直接翻译为Java代码(当然我们不能声明一个类直接继承java.lang.Enum,这里仅仅为了说明反编译后的枚举类的原型)如下:

public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration> {

	public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
super(name, ordinal);
this.type = type;
this.typeName = typeName;
} public Integer getType() {
return type;
} public String getTypeName() {
return typeName;
} public static PhoneOsEnumeration[] values() {
return $VALUES.clone();
} public static PhoneOsEnumeration valueOf(String name) {
return Enum.valueOf(PhoneOsEnumeration.class, name);
} private final Integer type;
private final String typeName;
public static final PhoneOsEnumeration ANDROID;
public static final PhoneOsEnumeration IOS;
private static final PhoneOsEnumeration[] $VALUES; static {
ANDROID = new PhoneOsEnumeration("ANDROID", 0, 1, "android");
IOS = new PhoneOsEnumeration("IOS", 1, 2, "ios");
$VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
}
}

概括来说就是成员变量都是通过静态代码块声明,这里注意一点父类Enum实例化的时候需要覆盖父类构造器protected Enum(String name, int ordinal),其他方法的实现都是十分简单。

JDK的枚举描述

国际惯例,先看一下JavaSE-8的语言规范中JLS-8.9对枚举类型的定义和描述:

感觉有点似曾相识,总结一下重要内容有以下几点:

  • 枚举的声明格式是:{ClassModifier} enum Identifier [Superinterfaces] EnumBody,ClassModifier是修饰符,Identifier是枚举的名称可以类比为类名,枚举类型可以实现接口。
  • 枚举类型不能使用abstract或者final修饰,否则会产生编译错误。
  • 枚举类型的直接超类是java.lang.Enum。
  • 枚举类型除了枚举常量定义之外没有其他实例,也就是枚举类型不能实例化。
  • 枚举类型禁用反射操作进行实例化(这个特性就是Effetive Java中推荐使用枚举实现单例的原因)。

枚举的公共父类java.lang.Enum的源码如下(已经去掉全部注释):

public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable { private final String name; public final String name() {
return name;
} private final int ordinal; 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() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
} 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);
} 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");
}
}

大部分方法都比较简单,值得注意的几点是:

  • 1、valueOf方法依赖到的Class<?>#enumConstantDirectory(),这个方法首次调用完成之后,结果会缓存在Class<?>#enumConstantDirectory变量中。
  • 2、Enum实现了Serializable接口,但是readObjectreadObjectNoData直接抛出了InvalidObjectException异常,注释说到是"防止默认的反序列化",这一点有点不明不白,既然禁用反序列化为何要实现Serializable接口,这里可能考虑到是否实现Serializable接口应该交给开发者决定。
  • 3、Enum禁用克隆。

小结

JDK中枚举的底层实现就是使用了enum关键字声明的枚举类编译后最终会变成public final修饰同时实现了泛型接口java.lang.Enum并且指定泛型参数为自身的普通Java类,而成员属性和方法实现相关都是在编译完成后就已经成型的,枚举类型的成员变量都是通过静态代码块声明的。

(本文完 c-1-d e-20181006)

JDK中枚举的底层实现的更多相关文章

  1. JDK中注解的底层实现

    前提 用Java快三年了,注解算是一个常用的类型,特别是在一些框架里面会大量使用注解做组件标识.配置或者策略.但是一直没有深入去探究JDK中的注解到底是什么,底层是怎么实现了?于是参考了一些资料,做了 ...

  2. 冷饭新炒:理解JDK中UUID的底层实现

    前提 UUID是Universally Unique IDentifier的缩写,翻译为通用唯一标识符或者全局唯一标识符.对于UUID的描述,下面摘录一下规范文件A Universally Uniqu ...

  3. JDK中的BitMap实现之BitSet源码分析

    前提 本文主要内容是分析JDK中的BitMap实现之java.util.BitSet的源码实现,基于JDK11编写,其他版本的JDK不一定合适. 文中的图比特低位实际应该是在右边,但是为了提高阅读体验 ...

  4. Java中集合框架,Collection接口、Set接口、List接口、Map接口,已经常用的它们的实现类,简单的JDK源码分析底层实现

    (一)集合框架: Java语言的设计者对常用的数据结构和算法做了一些规范(接口)和实现(实现接口的类).所有抽象出来的数据结构和操作(算法)统称为集合框架. 程序员在具体应用的时候,不必考虑数据结构和 ...

  5. 13万字详细分析JDK中Stream的实现原理

    前提 Stream是JDK1.8中首次引入的,距今已经过去了接近8年时间(JDK1.8正式版是2013年底发布的).Stream的引入一方面极大地简化了某些开发场景,另一方面也可能降低了编码的可读性( ...

  6. JDK中的URLConnection参数详解

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验 ...

  7. 转:JDK中的URLConnection参数详解

    针对JDK中的URLConnection连接Servlet的问题,网上有虽然有所涉及,但是只是说明了某一个或几个问题,是以FAQ的方式来解决的,而且比较零散,现在对这个类的使用就本人在项目中的使用经验 ...

  8. Java原子类中CAS的底层实现

    Java原子类中CAS的底层实现 从Java到c++到汇编, 深入讲解cas的底层原理. 介绍原理前, 先来一个Demo 以AtomicBoolean类为例.先来一个调用cas的demo. 主线程在f ...

  9. jdk中的简单并发,需要掌握

    前言 开心一刻 小时候有一次爸爸带我去偷村头别人家的梨子,我上树摘,爸爸在下面放风,正摘着主人来了,爸爸指着我破口大骂:臭小子,赶紧给我滚下来,敢偷吃别人家梨子,看我不打死你.主人家赶紧说:没事没事, ...

随机推荐

  1. [BZOJ1791][IOI2008]Island岛屿(环套树DP)

    同NOI2013快餐店(NOI出原题?),下面代码由于BZOJ栈空间过小会RE. 大致是对每个连通块找到环,在所有内向树做一遍DP,再在环上做两遍前缀和优化的DP. #include<cstdi ...

  2. 【SDOI2017】树点染色【线段树+LCT】

    本来只是想练练LCT,没想到是个线段树 对于操作1:诶新的颜色?这不是access吗? 也就是说,我们用一棵splay来表示一种颜色 操作2直接在LCT上乱搞-- 不对啊,操作3要查子树 诶好像是静态 ...

  3. iOS开发系列--音频播放、录音、

    音频 在iOS中音频播放从形式上可以分为音效播放和音乐播放.前者主要指的是一些短音频播放,通常作为点缀音频,对于这类音频不需要进行进度.循环等控制.后者指的是一些较长的音频,通常是主音频,对于这些音频 ...

  4. Nginx反向代理 实现Web负载均衡

    实现负载均衡的方式有很多种,DNS.反向代理.LVS负载均衡器(软件实现).F5(负载均衡器,硬件,非常昂贵)这里我们只提到基于DNS,以及反向代理的方式来实现负载均衡Web服务       DNS服 ...

  5. How Basic Performance Analysis Saved Us Millions-------火焰图

    ENGINEERING How Basic Performance Analysis Saved Us Millions  Michael Malis May 19, 2017 9 min read ...

  6. ORACLE 查看CPU使用率最高的语句及一些性能查询语句

    select * from (select sql_text,sql_id,cpu_time from v$sql order by cpu_time desc) where rownum<=1 ...

  7. ICLR 2013 International Conference on Learning Representations深度学习论文papers

    ICLR 2013 International Conference on Learning Representations May 02 - 04, 2013, Scottsdale, Arizon ...

  8. STL源码剖析——hashtable

    二叉搜索树具有对数时间的搜索复杂度,但是这样的复杂度是再输入数据有足够的随机性的假设上哈希表在插入删除搜索操作上也具有常数时间的表现,而且这种表现是以统计为基础,不需要依赖输入元素的随机性 hasht ...

  9. JMS基本概念之二

    JMS规范  JMS定义了Java中访问消息中间件的接口,并没有给予实现,实现JMS接口的消息中间件称为JMS Provider,例如ActiveMQ JMS provider: 实现JMS接口和规范 ...

  10. C/C++调用java---JNI常用函数

    DefineClass         jclass  DefineClass(JNIEnv *env, jobject loader,  const jbyte *buf, jsize bufLen ...