前提

上一篇文章复习介绍了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. Ajax 跨域问题(JSONP && Access-Control-Allow-Origin)

    1.使用jsonp跨域请求 2.通过设置服务端头部跨域请求 3.设置nginx/apach 使用jsonp跨域请求 什么是Jsonp JSONP(JSON with Padding)是一个非官方的协议 ...

  2. NOIP200606金明的预算方案

    试题描述: 金明今天很开心,家里购置的新房就要领钥匙了,新房里有一间金明自己专用的很宽敞的房间.更让他高兴的是,妈妈昨天对他说:“你的房间需要购买哪些物品,怎么布置,你说了算,只要不超过N元钱就行”. ...

  3. CROC 2016 - Elimination Round (Rated Unofficial Edition) A. Amity Assessment 水题

    A. Amity Assessment 题目连接: http://www.codeforces.com/contest/655/problem/A Description Bessie the cow ...

  4. leetcode47. Permutations II

    leetcode47. Permutations II 题意: 给定可能包含重复的数字的集合,返回所有可能的唯一排列. 思路: 这题全排列两种写法. 用hash表记录已经visit的数字,虽然看起来很 ...

  5. iOS防止button重复点击

    项目中常会遇到在按钮的点击事件中去执行一些耗时操作.如果处理不当经常会出现连续多次点击push多次的情况,造成不好的用户体验. 一种情况是用户快速连续点击,这种情况无法避免.另一种情况是点击一次后响应 ...

  6. Linux知识(1)----U盘安装Ubantu14.04系统

    由于需要用到ROS(Robot Operating System)机器人操作系统,该系统是基于Linux系统Ubantu14.04的,第一次安装接触Linux点点生惧,但我知道并没那么难弄,况且还是U ...

  7. hadoop招聘需求每天都在添加,短短半个月时间,需求量差点儿翻了一番,这是大数据要爆发的节奏么?

    近期常常关注企业hadoop招聘需求的动态变化,多说无益,直接上几张百度的截图: 4月20日: 4月22日: 4月27日: 5月8日:

  8. AC-PC线(前联合-后联合线)

    下面利用一张大脑矢状面(侧视图)来描述ac-pc的空间位置关系.前联合用红色点表示,后联合用黄色表示. 在Talairach 模板的官方文档中,AC-PC线从前联合AC的表面出发,延伸到后联合PC的中 ...

  9. 1644 免费馅饼 题解(c++)

    1644 免费馅饼(巴蜀oj上的编号) 题面:          SERKOI最新推出了一种叫做"免费馅饼"的游戏.         游戏在一个舞台上进行.舞台的宽度为W格,天幕的 ...

  10. Orchard运用 - 导入旧随笔导致归档的问题

    归档功能对于一个博客App这应该必须有的需求,故此Orchard中博客模块默认实现这一特性并通过一个叫"Blog Archives" widget, 你可以将其安装博客主页的侧边栏 ...