enum 详解

目录

enum 简介

环境介绍

jdk版本 1.8.0_102

基本使用

新建一个枚举Sex

package com.aya;

public enum Sex {
    MALE,FEMALE;

    public void goToilet(){
        System.out.println("去上厕所了");
    }
}

执行方法

    @Test
    public void testSex(){
        Sex male = Sex.MALE;
        male.goToilet();
    }

控制台输出: 去上厕所了

枚举实现方式

通过jdk自带的反编译工具 javap 进行反编译

  • -p -private 显示所有类和成员

执行 javap -p com/aya/Sex.class

public final class com.aya.Sex extends java.lang.Enum<com.aya.Sex> {
  public static final com.aya.Sex MALE;
  public static final com.aya.Sex FEMALE;
  private static final com.aya.Sex[] $VALUES;
  public static com.aya.Sex[] values();
  public static com.aya.Sex valueOf(java.lang.String);
  private com.aya.Sex();
  public void goToilet();
  static {};
}

得出结论: 枚举实际上就是一个”特殊的类”

既然是一个类,就可以自由定义静态方法,成员方法,实现接口等…

分析介绍

用命令将枚举反编译成java汇编代码

  • -p -private 显示所有类和成员
  • -v: 输出附加信息

执行: javap -v -p com/aya/Sex.class

获得枚举字节码内容

本文重点解析,三个语句块做了什么
1. 构造函数
1. static 静态块
2. valuesOf
3. values

然后对自定义函数 goToilet ,进行一个简单的解析

核心分析

枚举构造函数

字节码详情 摘抄构造函数

private com.aya.Sex();
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PRIVATE
    Code:
      stack=3, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: iload_2
         3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
         6: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/aya/Sex;
    Signature: #34                          // ()V

翻译为java代码

super(param1,param2)
  1. aload_0 是 this
  2. aload_1 是第一个String参数
  3. iload_2 是第二个int参数
  4. invokespecial #6 是常量池第6个索引位置的表。 表示执行父类构造方法
  5. return 当前方法返回void

static静态块

字节码详情 摘抄静态块

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #4                  // class com/aya/Sex
         3: dup
         4: ldc           #10                 // String MALE
         6: iconst_0
         7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #12                 // Field MALE:Lcom/aya/Sex;
        13: new           #4                  // class com/aya/Sex
        16: dup
        17: ldc           #13                 // String FEMALE
        19: iconst_1
        20: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #14                 // Field FEMALE:Lcom/aya/Sex;
        26: iconst_2
        27: anewarray     #4                  // class com/aya/Sex
        30: dup
        31: iconst_0
        32: getstatic     #12                 // Field MALE:Lcom/aya/Sex;
        35: aastore
        36: dup
        37: iconst_1
        38: getstatic     #14                 // Field FEMALE:Lcom/aya/Sex;
        41: aastore
        42: putstatic     #1                  // Field $VALUES:[Lcom/aya/Sex;
        45: return
      LineNumberTable:
        line 4: 0
        line 3: 26

将上面的字节码翻译为Java源码


String male= "MALE";//4
int maleNumber= 0;//6
Sex sexA = new Sex(male,maleNumber);//0-3 和 7

Sex.MALE = sexA; // 10

int maleNumberA= 0;//6
String female= "FEMALE";//17
int femaleNumber = 1;//19
Sex sexB = new Sex(female,femaleNumber);//13-16 和 20
Sex.FEMALE = sexB; // 23

int arrlength = 2;//26
Sex [] sexArray = new Sex[arrlength];//27-30
int numA = 0;//31
sexArray[numA]=Sex.MALE;//32-36
int numB = 1;//37
sexArray[numB]=Sex.FEMALE;//38-41
Sex.$VALUES = sexArray;//42
return;//45

接下来简化成我们平时写的代码:

Sex male = new Sex("MALE",0);
Sex female = new Sex("FEMALE",1);

Sex [] sexArray = new Sex[2];
sexArray[0]=male;
sexArray[1]=female;

Sex.$VALUES = sexArray;

静态块中,做了下面两件事
1. 依次为所有的字段赋值
2. 将所有枚举值依次加入到 $VALUES 数组中

values

字节码详情 摘抄出来values部分

public static com.aya.Sex[] values();
    descriptor: ()[Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field $VALUES:[Lcom/aya/Sex;
         3: invokevirtual #2                  // Method "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
         6: checkcast     #3                  // class "[Lcom/aya/Sex;"
         9: areturn
      LineNumberTable:
        line 3: 0

翻译为 java 代码

T [] arr = Sex.$VALUES;// 0
return (Sex[])arr.clone();//3-9

clone 一份数组返回.

所以如果你可以任意修改返回的数组,而不用担心对values的返回值有任何影响

valueOf

先从 字节码详情 摘抄出来valueOf部分

  public static com.aya.Sex valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #4                  // class com/aya/Sex
         2: aload_0
         3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #4                  // class com/aya/Sex
         9: areturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  name   Ljava/lang/String;

翻译为java代码

Class clazz = com.aya.Sex.class;//0
// param1 是第一个参数
Enum var = Enum.valueOf(clazz,param1);//2-3 
return (Sex)var;//6-9

事实上用的Enum的valueOf转换的. 接着跟进Enum.valueOf

public abstract class Enum<E extends Enum<E>>
        implements Comparable<E>, Serializable {
    //省略其他函数
    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);
    }
}
  1. 用Class对象的函数 enumConstantDirectory() 会返回一个Map

拓展

自定义方法

字节码详情 摘抄出来goToilet部分

public void goToilet();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #8                  // String 去上厕所了
         5: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/aya/Sex;

翻译为源码:

PrintStream print = java.lang.System.out;
print.println("去上厕所了");

当然我们写源码通常也不会把输出语句写成两行.

但是虚拟机在字节码的时候,得一步一步来. 我们还原的时候,可以根据我们的习惯进行还原

枚举常量字典


public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {

 Map<String, T> enumConstantDirectory() {
        if (enumConstantDirectory == null) {
            T[] universe = getEnumConstantsShared();
            if (universe == null)
                throw new IllegalArgumentException(
                    getName() + " is not an enum type");
            Map<String, T> m = new HashMap<>(2 * universe.length);
            for (T constant : universe)
                m.put(((Enum<?>)constant).name(), constant);
            enumConstantDirectory = m;
        }
        return enumConstantDirectory;
    }

    }

这个类是一个友元方法,只能同包调用,我们也调用不了

  1. 判断enumConstantDirectory不为null,返回
  2. enumConstantDirectory 为 null, getEnumConstantsShared 获取对象数组
  3. 将数组转换成map

问题就在于 getEnumConstantsShared 做了什么。如何通过 class 获取具体对象数组?

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
T[] getEnumConstantsShared() {
        if (enumConstants == null) {
            if (!isEnum()) return null;
            try {
                final Method values = getMethod("values");
                java.security.AccessController.doPrivileged(
                    new java.security.PrivilegedAction<Void>() {
                        public Void run() {
                                values.setAccessible(true);
                                return null;
                            }
                        });
                @SuppressWarnings("unchecked")
                T[] temporaryConstants = (T[])values.invoke(null);
                enumConstants = temporaryConstants;
            }
            // These can happen when users concoct enum-like classes
            // that don't comply with the enum spec.
            catch (InvocationTargetException | NoSuchMethodException |
                   IllegalAccessException ex) { return null; }
        }
        return enumConstants;
    }
}

还是Class类的一个友元方法

  1. 获取方法values
  2. 调用静态方法 values ,获得 T[]

method.invoke(null) 执行静态方法

method.invoke(obj) 执行对象的方法

这里事实上是约定的一个规则. 枚举类必须要有values 静态方法.

如果没有的话,调用values和valueOf 都会报错

字节码详情

Classfile /E:/workspace/source-analysis/java-core/enum-analysis/target/classes/com/aya/Sex.class
  Last modified 2018-7-5; size 1105 bytes
  MD5 checksum 43e1d49d4d9f7023f32173510d1b38db
  Compiled from "Sex.java"
public final class com.aya.Sex extends java.lang.Enum<com.aya.Sex>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
   #1 = Fieldref           #4.#40         // com/aya/Sex.$VALUES:[Lcom/aya/Sex;
   #2 = Methodref          #41.#42        // "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
   #3 = Class              #20            // "[Lcom/aya/Sex;"
   #4 = Class              #43            // com/aya/Sex
   #5 = Methodref          #15.#44        // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
   #6 = Methodref          #15.#45        // java/lang/Enum."<init>":(Ljava/lang/String;I)V
   #7 = Fieldref           #46.#47        // java/lang/System.out:Ljava/io/PrintStream;
   #8 = String             #48            // 去上厕所了
   #9 = Methodref          #49.#50        // java/io/PrintStream.println:(Ljava/lang/String;)V
  #10 = String             #16            // MALE
  #11 = Methodref          #4.#45         // com/aya/Sex."<init>":(Ljava/lang/String;I)V
  #12 = Fieldref           #4.#51         // com/aya/Sex.MALE:Lcom/aya/Sex;
  #13 = String             #18            // FEMALE
  #14 = Fieldref           #4.#52         // com/aya/Sex.FEMALE:Lcom/aya/Sex;
  #15 = Class              #53            // java/lang/Enum
  #16 = Utf8               MALE
  #17 = Utf8               Lcom/aya/Sex;
  #18 = Utf8               FEMALE
  #19 = Utf8               $VALUES
  #20 = Utf8               [Lcom/aya/Sex;
  #21 = Utf8               values
  #22 = Utf8               ()[Lcom/aya/Sex;
  #23 = Utf8               Code
  #24 = Utf8               LineNumberTable
  #25 = Utf8               valueOf
  #26 = Utf8               (Ljava/lang/String;)Lcom/aya/Sex;
  #27 = Utf8               LocalVariableTable
  #28 = Utf8               name
  #29 = Utf8               Ljava/lang/String;
  #30 = Utf8               <init>
  #31 = Utf8               (Ljava/lang/String;I)V
  #32 = Utf8               this
  #33 = Utf8               Signature
  #34 = Utf8               ()V
  #35 = Utf8               goToilet
  #36 = Utf8               <clinit>
  #37 = Utf8               Ljava/lang/Enum<Lcom/aya/Sex;>;
  #38 = Utf8               SourceFile
  #39 = Utf8               Sex.java
  #40 = NameAndType        #19:#20        // $VALUES:[Lcom/aya/Sex;
  #41 = Class              #20            // "[Lcom/aya/Sex;"
  #42 = NameAndType        #54:#55        // clone:()Ljava/lang/Object;
  #43 = Utf8               com/aya/Sex
  #44 = NameAndType        #25:#56        // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  #45 = NameAndType        #30:#31        // "<init>":(Ljava/lang/String;I)V
  #46 = Class              #57            // java/lang/System
  #47 = NameAndType        #58:#59        // out:Ljava/io/PrintStream;
  #48 = Utf8               去上厕所了
  #49 = Class              #60            // java/io/PrintStream
  #50 = NameAndType        #61:#62        // println:(Ljava/lang/String;)V
  #51 = NameAndType        #16:#17        // MALE:Lcom/aya/Sex;
  #52 = NameAndType        #18:#17        // FEMALE:Lcom/aya/Sex;
  #53 = Utf8               java/lang/Enum
  #54 = Utf8               clone
  #55 = Utf8               ()Ljava/lang/Object;
  #56 = Utf8               (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  #57 = Utf8               java/lang/System
  #58 = Utf8               out
  #59 = Utf8               Ljava/io/PrintStream;
  #60 = Utf8               java/io/PrintStream
  #61 = Utf8               println
  #62 = Utf8               (Ljava/lang/String;)V
{
  public static final com.aya.Sex MALE;
    descriptor: Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final com.aya.Sex FEMALE;
    descriptor: Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  private static final com.aya.Sex[] $VALUES;
    descriptor: [Lcom/aya/Sex;
    flags: ACC_PRIVATE, ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

  public static com.aya.Sex[] values();
    descriptor: ()[Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=0, args_size=0
         0: getstatic     #1                  // Field $VALUES:[Lcom/aya/Sex;
         3: invokevirtual #2                  // Method "[Lcom/aya/Sex;".clone:()Ljava/lang/Object;
         6: checkcast     #3                  // class "[Lcom/aya/Sex;"
         9: areturn
      LineNumberTable:
        line 3: 0

  public static com.aya.Sex valueOf(java.lang.String);
    descriptor: (Ljava/lang/String;)Lcom/aya/Sex;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: ldc           #4                  // class com/aya/Sex
         2: aload_0
         3: invokestatic  #5                  // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
         6: checkcast     #4                  // class com/aya/Sex
         9: areturn
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      10     0  name   Ljava/lang/String;

  private com.aya.Sex();
    descriptor: (Ljava/lang/String;I)V
    flags: ACC_PRIVATE
    Code:
      stack=3, locals=3, args_size=3
         0: aload_0
         1: aload_1
         2: iload_2
         3: invokespecial #6                  // Method java/lang/Enum."<init>":(Ljava/lang/String;I)V
         6: return
      LineNumberTable:
        line 3: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       7     0  this   Lcom/aya/Sex;
    Signature: #34                          // ()V

  public void goToilet();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #7                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #8                  // String 去上厕所了
         5: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 7: 0
        line 8: 8
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0       9     0  this   Lcom/aya/Sex;

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=4, locals=0, args_size=0
         0: new           #4                  // class com/aya/Sex
         3: dup
         4: ldc           #10                 // String MALE
         6: iconst_0
         7: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
        10: putstatic     #12                 // Field MALE:Lcom/aya/Sex;
        13: new           #4                  // class com/aya/Sex
        16: dup
        17: ldc           #13                 // String FEMALE
        19: iconst_1
        20: invokespecial #11                 // Method "<init>":(Ljava/lang/String;I)V
        23: putstatic     #14                 // Field FEMALE:Lcom/aya/Sex;
        26: iconst_2
        27: anewarray     #4                  // class com/aya/Sex
        30: dup
        31: iconst_0
        32: getstatic     #12                 // Field MALE:Lcom/aya/Sex;
        35: aastore
        36: dup
        37: iconst_1
        38: getstatic     #14                 // Field FEMALE:Lcom/aya/Sex;
        41: aastore
        42: putstatic     #1                  // Field $VALUES:[Lcom/aya/Sex;
        45: return
      LineNumberTable:
        line 4: 0
        line 3: 26
}
Signature: #37                          // Ljava/lang/Enum<Lcom/aya/Sex;>;
SourceFile: "Sex.java"

鸣谢

java 字节码指令集和功能描述: https://blog.csdn.net/sum_rain/article/details/39892219

猜你喜欢

转载自blog.csdn.net/mz4138/article/details/80927656