Enum 类声明

public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable

可以看到,它是一个抽象类,实现的 ComparableSerializable 接口

Enum 类的属性

  • private final String name; 所代表的名称(即 enum 的名称,也即是枚举字段的变量的字面量所表示的字符串,这个并不能改变的,因为它是 final 的)
  • private final int ordinal; 所代表的顺序(即 enum 声明的顺序,从0开始,这个并不能改变的,因为它是 final 的)

Enum 类的方法

  • protected Enum(String name, int ordinal); 构造函数

  • public final String name(); 获取它的 name

  • public final int ordinal(); 获取它的 ordinal

  • public static > T valueOf(Class enumType, String name); 返回指定 name 所代表的枚举类型

枚举类

package org.agoncal.sample.jmh;

/**
 * Created by emacsist on 2017/7/3.
 */
public enum Hello {
    HELLO,
    WORLD
}

枚举类的字节码及分析

[15:27:28] emacsist:classes $ javap -verbose org.agoncal.sample.jmh.Hello
Classfile /Users/emacsist/Documents/idea/logging/target/classes/org/agoncal/sample/jmh/Hello.class
  Last modified 2017-7-3; size 981 bytes
  MD5 checksum 2ee3f1a21018896c715ed7e8db96fad6
  Compiled from "Hello.java"
public final class org.agoncal.sample.jmh.Hello extends java.lang.Enum<org.agoncal.sample.jmh.Hello>
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER, ACC_ENUM
Constant pool:
   #1 = Fieldref           #4.#36         // org/agoncal/sample/jmh/Hello.$VALUES:[Lorg/agoncal/sample/jmh/Hello;
   #2 = Methodref          #37.#38        // "[Lorg/agoncal/sample/jmh/Hello;".clone:()Ljava/lang/Object;
   #3 = Class              #17            // "[Lorg/agoncal/sample/jmh/Hello;"
   #4 = Class              #39            // org/agoncal/sample/jmh/Hello
   #5 = Methodref          #12.#40        // java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
   #6 = Methodref          #12.#41        // java/lang/Enum."<init>":(Ljava/lang/String;I)V
   #7 = String             #13            // HELLO
   #8 = Methodref          #4.#41         // org/agoncal/sample/jmh/Hello."<init>":(Ljava/lang/String;I)V
   #9 = Fieldref           #4.#42         // org/agoncal/sample/jmh/Hello.HELLO:Lorg/agoncal/sample/jmh/Hello;
  #10 = String             #15            // WORLD
  #11 = Fieldref           #4.#43         // org/agoncal/sample/jmh/Hello.WORLD:Lorg/agoncal/sample/jmh/Hello;
  #12 = Class              #44            // java/lang/Enum
  #13 = Utf8               HELLO
  #14 = Utf8               Lorg/agoncal/sample/jmh/Hello;
  #15 = Utf8               WORLD
  #16 = Utf8               $VALUES
  #17 = Utf8               [Lorg/agoncal/sample/jmh/Hello;
  #18 = Utf8               values
  #19 = Utf8               ()[Lorg/agoncal/sample/jmh/Hello;
  #20 = Utf8               Code
  #21 = Utf8               LineNumberTable
  #22 = Utf8               valueOf
  #23 = Utf8               (Ljava/lang/String;)Lorg/agoncal/sample/jmh/Hello;
  #24 = Utf8               LocalVariableTable
  #25 = Utf8               name
  #26 = Utf8               Ljava/lang/String;
  #27 = Utf8               <init>
  #28 = Utf8               (Ljava/lang/String;I)V
  #29 = Utf8               this
  #30 = Utf8               Signature
  #31 = Utf8               ()V
  #32 = Utf8               <clinit>
  #33 = Utf8               Ljava/lang/Enum<Lorg/agoncal/sample/jmh/Hello;>;
  #34 = Utf8               SourceFile
  #35 = Utf8               Hello.java
  #36 = NameAndType        #16:#17        // $VALUES:[Lorg/agoncal/sample/jmh/Hello;
  #37 = Class              #17            // "[Lorg/agoncal/sample/jmh/Hello;"
  #38 = NameAndType        #45:#46        // clone:()Ljava/lang/Object;
  #39 = Utf8               org/agoncal/sample/jmh/Hello
  #40 = NameAndType        #22:#47        // valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
  #41 = NameAndType        #27:#28        // "<init>":(Ljava/lang/String;I)V
  #42 = NameAndType        #13:#14        // HELLO:Lorg/agoncal/sample/jmh/Hello;
  #43 = NameAndType        #15:#14        // WORLD:Lorg/agoncal/sample/jmh/Hello;
  #44 = Utf8               java/lang/Enum
  #45 = Utf8               clone
  #46 = Utf8               ()Ljava/lang/Object;
  #47 = Utf8               (Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum;
{
  public static final org.agoncal.sample.jmh.Hello HELLO;
    descriptor: Lorg/agoncal/sample/jmh/Hello;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

  public static final org.agoncal.sample.jmh.Hello WORLD;
    descriptor: Lorg/agoncal/sample/jmh/Hello;
    flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM

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

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

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

可看到,编译器为我们自动将枚举字段变成:

public static final org.agoncal.sample.jmh.Hello HELLO;
public static final org.agoncal.sample.jmh.Hello WORLD;

它还为我们生成了以下这些方法:

// 返回该枚举类的所有 value
public static org.agoncal.sample.jmh.Hello[] values();

// 返回 String 表示的枚举对象
public static org.agoncal.sample.jmh.Hello valueOf(java.lang.String);

还有一个静态语句块(它的作用,就是为枚举类生成相应的默认的构造函数代码)

static {};

4: ldc           #7                  // String HELLO
6: iconst_0

17: ldc           #10                 // String WORLD
19: iconst_1

可以看到,编译器为我们自动生成的代码中,默认的 name 就是与字段的名称相同,ordinal 则按声明的顺序,从0开始递增。

构造完成后,分别将构造完成的对象,放到编译器自动生成的字段数组中:

$VALUES

自动生成的 values() 方法返回的就这个字段的所代表的数组了。

switch 与 枚举类

如果枚举类,用在 switch 语句上,Java编译器还会生成一个特殊的内部类:

    public static final void s(Hello hello) {
        switch (hello) {
            case WORLD:
                System.out.println("world");
            case HELLO:
                System.out.println("hello");
                break;
            default:
                System.out.println("default");
        }
    }

比如这段代码,Java编译器还会额外生成一个内部类:

SourceFile: "Test.java"
EnclosingMethod: #21.#0                 // org.agoncal.sample.jmh.Test
InnerClasses:
     static #7; //class org/agoncal/sample/jmh/Test$1

Test$1的字节码如下:

{
  static final int[] $SwitchMap$org$agoncal$sample$jmh$Hello;
    descriptor: [I
    flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=1, args_size=0
         0: invokestatic  #1                  // Method org/agoncal/sample/jmh/Hello.values:()[Lorg/agoncal/sample/jmh/Hello;
         3: arraylength
         4: newarray       int
         6: putstatic     #2                  // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
         9: getstatic     #2                  // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
        12: getstatic     #3                  // Field org/agoncal/sample/jmh/Hello.WORLD:Lorg/agoncal/sample/jmh/Hello;
        15: invokevirtual #4                  // Method org/agoncal/sample/jmh/Hello.ordinal:()I
        18: iconst_1
        19: iastore
        20: goto          24
        23: astore_0
        24: getstatic     #2                  // Field $SwitchMap$org$agoncal$sample$jmh$Hello:[I
        27: getstatic     #6                  // Field org/agoncal/sample/jmh/Hello.HELLO:Lorg/agoncal/sample/jmh/Hello;
        30: invokevirtual #4                  // Method org/agoncal/sample/jmh/Hello.ordinal:()I
        33: iconst_2
        34: iastore
        35: goto          39
        38: astore_0
        39: return
      Exception table:
         from    to  target type
             9    20    23   Class java/lang/NoSuchFieldError
            24    35    38   Class java/lang/NoSuchFieldError
      LineNumberTable:
        line 11: 0
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
      StackMapTable: number_of_entries = 4
        frame_type = 87 /* same_locals_1_stack_item */
          stack = [ class java/lang/NoSuchFieldError ]
        frame_type = 0 /* same */
        frame_type = 77 /* same_locals_1_stack_item */
          stack = [ class java/lang/NoSuchFieldError ]
        frame_type = 0 /* same */
}

可以看到,这个额外生成的类的作用:

  1. 声明了一个名为 static final int[] $SwitchMap$org$agoncal$sample$jmh$Helloint[] 数组。
  2. 用静态块语句来为这个数组进行初始化

伪代码如下:

static final int[] $SwitchMap$org$agoncal$sample$jmh$Hello;

static {
    $SwitchMap$org$agoncal$sample$jmh$Hello = new int[org.agoncal.sample.jmh.Hello.values().length];

     try {
            $SwitchMap$org$agoncal$sample$jmh$Hello[Hello.WORLD.ordinal()] = 1;
        } catch (NoSuchFieldError var2) {
            ;
        }

        try {
            $SwitchMap$org$agoncal$sample$jmh$Hello[Hello.HELLO.ordinal()] = 2;
        } catch (NoSuchFieldError var1) {
            ;
        }
}

注意,这个 SwitchMap 的索引是枚举类型的 ordinal 的值,然后它对应的值是从 1 开始的。

可以看到,上面的 swith 的代码生成的字节码如下:

         8: lookupswitch  { // 2
                       1: 36
                       2: 44
                 default: 55
            }

1:36 中的 1 就表示通过 SwitchMap[ordinal] 来获取它的值,然后用相对应的 int 值来直接对比它们是否相等。

想覆盖 name, ordinal 的值?

如果我们在自定义的枚举类中,手动声明了 name, ordinal ,会有什么情况?

比如:

package org.agoncal.sample.jmh;

/**
 * Created by emacsist on 2017/7/3.
 */
public enum Hello {
    HELLO("hello", 1) ,
    WORLD("world", 2);

    private String name;
    private int ordinal;

    Hello(String name, int ordinal){
        this.name = name;
        this.ordinal = ordinal;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getOrdinal() {
        return ordinal;
    }

    public void setOrdinal(int ordinal) {
        this.ordinal = ordinal;
    }
}

可以看到,Java编译器自动为我们生成的代码的字节码如下:

  static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=6, locals=0, args_size=0
         0: new           #4                  // class org/agoncal/sample/jmh/Hello
         3: dup
         4: ldc           #9                  // String HELLO
         6: iconst_0
         7: ldc           #10                 // String hello
         9: iconst_1
        10: invokespecial #11                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;I)V
        13: putstatic     #12                 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
        16: new           #4                  // class org/agoncal/sample/jmh/Hello
        19: dup
        20: ldc           #13                 // String WORLD
        22: iconst_1
        23: ldc           #14                 // String world
        25: iconst_2
        26: invokespecial #11                 // Method "<init>":(Ljava/lang/String;ILjava/lang/String;I)V
        29: putstatic     #15                 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
        32: iconst_2
        33: anewarray     #4                  // class org/agoncal/sample/jmh/Hello
        36: dup
        37: iconst_0
        38: getstatic     #12                 // Field HELLO:Lorg/agoncal/sample/jmh/Hello;
        41: aastore
        42: dup
        43: iconst_1
        44: getstatic     #15                 // Field WORLD:Lorg/agoncal/sample/jmh/Hello;
        47: aastore
        48: putstatic     #1                  // Field $VALUES:[Lorg/agoncal/sample/jmh/Hello;
        51: return

可以推测伪代码如下:

构造函数变为:

Hello(String, int, String, int){
}

Hello.HELLO = new Hello("HELLO", 0, "hello", 1);
Hello.WORLD = new Hello("WORLD", 1, "world", 2);

可以看到,虽然我们自定义了两个字段,即使它与 Enum 类中的相同,但Java编译器在为我们自动生成代码时,会一起保存的,这就确保了调用 Enumname() 方法,返回的一定是枚举字段的字面量的值, ordinal() 方法,返回的是枚举声明的顺序的值了。