JDK中枚举的底层实现

前提

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

通过例子查找本质

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

 1package club.throwable.enumeration;
2
3public enum PhoneOsEnum {
4
5    /**
6     * 安卓
7     */

8    ANDROID(1"android"),
9
10    /**
11     * ios
12     */

13    IOS(2"ios");
14
15
16    private final Integer type;
17    private final String typeName;
18
19    PhoneOsEnum(Integer type, String typeName) {
20        this.type = type;
21        this.typeName = typeName;
22    }
23
24    public Integer getType() {
25        return type;
26    }
27
28    public String getTypeName() {
29        return typeName;
30    }
31}

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

1javap -c -v D:Projectsrxjava-seedtargetclassesclubthrowableenumerationPhoneOsEnum.class

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

  1Classfile /D:/Projects/rxjava-seed/target/classes/club/throwable/enumeration/PhoneOsEnum.class
2  Last modified 2018-10-6; size 1561 bytes
3  MD5 checksum 6d3186042f54233219000927a2f196aa
4  Compiled from "PhoneOsEnum.java"
5public final class club.throwable.enumeration.PhoneOsEnum extends java.lang.Enum<club.throwable.enumeration.PhoneOsEnum>
6  minor version: 0
7  major version: 52
8  flagsACC_PUBLICACC_FINALACC_SUPERACC_ENUM
9Constant pool:
10   #1 
= Fieldref           #4.#49         // club/throwable/enumeration/PhoneOsEnum.$VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
11   #2 = Methodref          #50.#51        // "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
12   #3 = Class              #26            // "[Lclub/throwable/enumeration/PhoneOsEnum;"
13   #4 = Class              #52            // club/throwable/enumeration/PhoneOsEnum
14   #5 = Methodref          #17.#53        // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
15   #6 = Methodref          #17.#54        // java/lang/Enum."<init>":(Ljava/lang/String;I)V
16   #7 = Fieldref           #4.#55         // club/throwable/enumeration/PhoneOsEnum.type:Ljava/lang/Integer;
17   #8 = Fieldref           #4.#56         // club/throwable/enumeration/PhoneOsEnum.typeName:Ljava/lang/String;
18   #9 = String             #18            // ANDROID
19  #10 = Methodref          #57.#58        // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
20  #11 = String             #59            // android
21  #12 = Methodref          #4.#60         // club/throwable/enumeration/PhoneOsEnum."<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
22  #13 = Fieldref           #4.#61         // club/throwable/enumeration/PhoneOsEnum.ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
23  #14 = String             #20            // IOS
24  #15 = String             #62            // ios
25  #16 = Fieldref           #4.#63         // club/throwable/enumeration/PhoneOsEnum.IOS:Lclub/throwable/enumeration/PhoneOsEnum;
26  #17 = Class              #64            // java/lang/Enum
27  #18 = Utf8               ANDROID
28  #19 = Utf8               Lclub/throwable/enumeration/PhoneOsEnum;
29  #20 = Utf8               IOS
30  #21 = Utf8               type
31  #22 = Utf8               Ljava/lang/Integer;
32  #23 = Utf8               typeName
33  #24 = Utf8               Ljava/lang/String;
34  #25 = Utf8               $VALUES
35  #26 = Utf8               [Lclub/throwable/enumeration/PhoneOsEnum;
36  #27 = Utf8               values
37  #28 = Utf8               ()[Lclub/throwable/enumeration/PhoneOsEnum;
38  #29 = Utf8               Code
39  #30 = Utf8               LineNumberTable
40  #31 = Utf8               valueOf
41  #32 = Utf8               (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
42  #33 = Utf8               LocalVariableTable
43  #34 = Utf8               name
44  #35 = Utf8               <init>
45  #36 = Utf8               (Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
46  #37 = Utf8               this
47  #38 = Utf8               Signature
48  #39 = Utf8               (Ljava/lang/Integer;Ljava/lang/String;)V
49  #40 = Utf8               getType
50  #41 = Utf8               ()Ljava/lang/Integer;
51  #42 = Utf8               getTypeName
52  #43 = Utf8               ()Ljava/lang/String;
53  #44 = Utf8               <clinit>
54  #45 = Utf8               ()V
55  #46 = Utf8               Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
56  #47 = Utf8               SourceFile
57  #48 = Utf8               PhoneOsEnum.java
58  #49 = NameAndType        #25:#26        // $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
59  #50 = Class              #26            // "[Lclub/throwable/enumeration/PhoneOsEnum;"
60  #51 = NameAndType        #65:#66        // clone:()Ljava/lang/Object;
61  #52 = Utf8               club/throwable/enumeration/PhoneOsEnum
62  #53 = NameAndType        #31:#67        // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
63  #54 = NameAndType        #35:#68        // "<init>":(Ljava/lang/String;I)V
64  #55 = NameAndType        #21:#22        // type:Ljava/lang/Integer;
65  #56 = NameAndType        #23:#24        // typeName:Ljava/lang/String;
66  #57 = Class              #69            // java/lang/Integer
67  #58 = NameAndType        #31:#70        // valueOf:(I)Ljava/lang/Integer;
68  #59 = Utf8               android
69  #60 = NameAndType        #35:#36        // "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
70  #61 = NameAndType        #18:#19        // ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
71  #62 = Utf8               ios
72  #63 = NameAndType        #20:#19        // IOS:Lclub/throwable/enumeration/PhoneOsEnum;
73  #64 = Utf8               java/lang/Enum
74  #65 = Utf8               clone
75  #66 = Utf8               ()Ljava/lang/Object;
76  #67 = Utf8               (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
77  #68 = Utf8               (Ljava/lang/String;I)V
78  #69 = Utf8               java/lang/Integer
79  #70 = Utf8               (I)Ljava/lang/Integer;
80{
81  public static final club.throwable.enumeration.PhoneOsEnum ANDROID;
82    descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
83    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
84
85  public static final club.throwable.enumeration.PhoneOsEnum IOS;
86    descriptor: Lclub/throwable/enumeration/PhoneOsEnum;
87    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM
88
89  public static club.throwable.enumeration.PhoneOsEnum[] values();
90    descriptor: ()[Lclub/throwable/enumeration/PhoneOsEnum;
91    flags: ACC_PUBLIC, ACC_STATIC
92    Code:
93      stack=1, locals=0, args_size=0
94         0: getstatic     #1                  // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
95         3: invokevirtual #2                  // Method "[Lclub/throwable/enumeration/PhoneOsEnum;".clone:()Ljava/lang/Object;
96         6: checkcast     #3                  // class "[Lclub/throwable/enumeration/PhoneOsEnum;"
97         9: areturn
98      LineNumberTable:
99        line 90
100
101  public static club.throwable.enumeration.PhoneOsEnum valueOf(java.lang.String);
102    descriptor: (Ljava/lang/String;)Lclub/throwable/enumeration/PhoneOsEnum;
103    flags: ACC_PUBLIC, ACC_STATIC
104    Code:
105      stack=2, locals=1, args_size=1
106         0: ldc           #4                  // class club/throwable/enumeration/PhoneOsEnum
107         2: aload_0
108         3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
109         6: checkcast     #4                  // class club/throwable/enumeration/PhoneOsEnum
110         9: areturn
111      LineNumberTable:
112        line 90
113      LocalVariableTable:
114        Start  Length  Slot  Name   Signature
115            0      10     0  name   Ljava/lang/String;
116
117  public java.lang.Integer getType();
118    descriptor: ()Ljava/lang/Integer;
119    flags: ACC_PUBLIC
120    Code:
121      stack=1, locals=1, args_size=1
122         0: aload_0
123         1: getfield      #7                  // Field type:Ljava/lang/Integer;
124         4: areturn
125      LineNumberTable:
126        line 310
127      LocalVariableTable:
128        Start  Length  Slot  Name   Signature
129            0       5     0  this   Lclub/throwable/enumeration/PhoneOsEnum;
130
131  public java.lang.String getTypeName();
132    descriptor: ()Ljava/lang/String;
133    flags: ACC_PUBLIC
134    Code:
135      stack=1, locals=1, args_size=1
136         0: aload_0
137         1: getfield      #8                  // Field typeName:Ljava/lang/String;
138         4: areturn
139      LineNumberTable:
140        line 350
141      LocalVariableTable:
142        Start  Length  Slot  Name   Signature
143            0       5     0  this   Lclub/throwable/enumeration/PhoneOsEnum;
144
145  static {};
146    descriptor: ()V
147    flags: ACC_STATIC
148    Code:
149      stack=6, locals=0, args_size=0
150         0new           #4                  // class club/throwable/enumeration/PhoneOsEnum
151         3: dup
152         4: ldc           #9                  // String ANDROID
153         6: iconst_0
154         7: iconst_1
155         8: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
156        11: ldc           #11                 // String android
157        13: invokespecial #12                 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
158        16: putstatic     #13                 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
159        19new           #4                  // class club/throwable/enumeration/PhoneOsEnum
160        22: dup
161        23: ldc           #14                 // String IOS
162        25: iconst_1
163        26: iconst_2
164        27: invokestatic  #10                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
165        30: ldc           #15                 // String ios
166        32: invokespecial #12                 // Method "<init>":(Ljava/lang/String;ILjava/lang/Integer;Ljava/lang/String;)V
167        35: putstatic     #16                 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
168        38: iconst_2
169        39: anewarray     #4                  // class club/throwable/enumeration/PhoneOsEnum
170        42: dup
171        43: iconst_0
172        44: getstatic     #13                 // Field ANDROID:Lclub/throwable/enumeration/PhoneOsEnum;
173        47: aastore
174        48: dup
175        49: iconst_1
176        50: getstatic     #16                 // Field IOS:Lclub/throwable/enumeration/PhoneOsEnum;
177        53: aastore
178        54: putstatic     #1                  // Field $VALUES:[Lclub/throwable/enumeration/PhoneOsEnum;
179        57return
180      LineNumberTable:
181        line 140
182        line 1919
183        line 938
184}
185Signature: #46                          // Ljava/lang/Enum<Lclub/throwable/enumeration/PhoneOsEnum;>;
186SourceFile: "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,这里仅仅为了说明反编译后的枚举类的原型)如下:

 1public final class PhoneOsEnumeration extends Enum<PhoneOsEnumeration{
2
3    public PhoneOsEnumeration(String name, int ordinal, Integer type, String typeName) {
4        super(name, ordinal);
5        this.type = type;
6        this.typeName = typeName;
7    }
8
9    public Integer getType() {
10        return type;
11    }
12
13    public String getTypeName() {
14        return typeName;
15    }
16
17    public static PhoneOsEnumeration[] values() {
18        return $VALUES.clone();
19    }
20
21    public static PhoneOsEnumeration valueOf(String name) {
22        return Enum.valueOf(PhoneOsEnumeration.class, name);
23    }
24
25    private final Integer type;
26    private final String typeName;
27    public static final PhoneOsEnumeration ANDROID;
28    public static final PhoneOsEnumeration IOS;
29    private static final PhoneOsEnumeration[] $VALUES;
30
31    static {
32        ANDROID = new PhoneOsEnumeration("ANDROID"01"android");
33        IOS = new PhoneOsEnumeration("IOS"12"ios");
34        $VALUES = new PhoneOsEnumeration[]{ANDROID, IOS};
35    }
36}

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

JDK的枚举描述

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

JDK中枚举的底层实现

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

  • 枚举的声明格式是:{ClassModifier} enum Identifier [Superinterfaces] EnumBodyClassModifier是修饰符,Identifier是枚举的名称可以类比为类名,枚举类型可以实现接口

  • 枚举类型不能使用abstract或者final修饰,否则会产生编译错误。

  • 枚举类型的直接超类是java.lang.Enum

  • 枚举类型除了枚举常量定义之外没有其他实例,也就是枚举类型不能实例化。

  • 枚举类型禁用反射操作进行实例化(这个特性就是Effetive Java中推荐使用枚举实现单例的原因)。

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

 1public abstract class Enum<E extends Enum<E>>
2        implements Comparable<E>, Serializable 
{
3
4    private final String name; 
5
6    public final String name() {
7        return name;
8    } 
9
10    private final int ordinal;
11
12    public final int ordinal() {
13        return ordinal;
14    }
15
16    protected Enum(String name, int ordinal) {
17        this.name = name;
18        this.ordinal = ordinal;
19    }
20
21    public String toString() {
22        return name;
23    }
24
25    public final boolean equals(Object other) {
26        return this==other;
27    }
28
29    public final int hashCode() {
30        return super.hashCode();
31    } 
32
33    protected final Object clone() throws CloneNotSupportedException {
34        throw new CloneNotSupportedException();
35    }  
36
37    public final int compareTo(E o) {
38        Enum<?> other = (Enum<?>)o;
39        Enum<E> self = this;
40        if (self.getClass() != other.getClass() && // optimization
41            self.getDeclaringClass() != other.getDeclaringClass())
42            throw new ClassCastException();
43        return self.ordinal - other.ordinal;
44    }
45
46    public final Class<E> getDeclaringClass() {
47        Class<?> clazz = getClass();
48        Class<?> zuper = clazz.getSuperclass();
49        return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
50    } 
51
52    public static <T extends Enum<T>> valueOf(Class<T> enumType,
53                                                String name)
 
{
54        T result = enumType.enumConstantDirectory().get(name);
55        if (result != null)
56            return result;
57        if (name == null)
58            throw new NullPointerException("Name is null");
59        throw new IllegalArgumentException(
60            "No enum constant " + enumType.getCanonicalName() + "." + name);
61    }  
62
63    protected final void finalize() { }
64
65    private void readObject(ObjectInputStream in) throws IOException,
66        ClassNotFoundException 
{
67        throw new InvalidObjectException("can't deserialize enum");
68    }
69
70    private void readObjectNoData() throws ObjectStreamException {
71        throw new InvalidObjectException("can't deserialize enum");
72    }                              
73}            

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

  • 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 r-a-20200412修复排版)


原文始发于微信公众号(Throwable):JDK中枚举的底层实现

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/38613.html

(0)
小半的头像小半

相关推荐

发表回复

登录后才能评论
极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!