字节码层面理解枚举类Enum在switch中的使用

1.本文从字节码层面来探究,switch的case变量值为java枚举Enum时,编译器究竟做了什么工作。

2.源码如下

枚举类Color

public enum Color {
    RED,BLUE,YELLOW
}

switch中使用Color

public class MySwitchTest {
    int chooseColor(Color c){
        switch (c){
            case RED:
                return 5;
            case RED:
                return 6;
            case YELLOW:
                return 7;
            default:
                return 8;
        }
    }
}

3.先看结果,javac编译后,MySwitchTest生成了一个内部类MySwitchTest$1,反编译后的MySwitchTest的代码大致如下:

public class MySwitchTest {


	int chooseColor(Color c){
		int a = MySwitchTest$1.$SwitchMap$Color[c.ordinal];
		switch (a){
			case 1:
				return 5;
			case 2:
				return 6;
			case 3:
				return 7;
			default:
				return 8;
		}
	}
	

	
	static class MySwitchTest$1 {

		static final int[] $SwitchMap$Color;
		
		static {
			Color[] values = Color.values();
			int length = values.length;
			$SwitchMap$Color = new int[length];
			
			try {
				$SwitchMap$Color[Color.RED.ordinal()] = 1;	//和switch中的case 1对应
			} catch (NoSuchFieldError e) {
			}
			
			try {
				$SwitchMap$Color[Color.BLUE.ordinal()] = 2; //和switch中的case 2对应
			} catch (NoSuchFieldError e) {
			}

			try {
				$SwitchMap$Color[Color.YELLOW.ordinal()] = 3; //和switch中的case 3对应
			} catch (NoSuchFieldError e) {
			}

		}
	}
}

4.我们来看MySwitchTest.java编译后生成的class文件,除了一个MySwitchTest.class外,还生成了一个MySwitchTest$1.class文件,MySwitchTest$1是MySwitchTest的一个内部类,是编译器javac在编译MySwitchTest.java时自动生成的。

运行 javap -v -p MySwitchTest\$1.class,可以得到内部类的字节码如下

Classfile /C:/workspace/test/target/classes/MySwitchTest$1.class
  Last modified 2019-12-16; size 619 bytes
  MD5 checksum 26074600ed9a7634433a37fd542bc348
  Compiled from "MySwitchTest.java"
class MySwitchTest$1
  minor version: 0
  major version: 52
  flags: ACC_SUPER, ACC_SYNTHETIC
Constant pool:
...
{
  static final int[] $SwitchMap$Color;
    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 Color.values:()[LColor;
         3: arraylength
         4: newarray       int
         6: putstatic     #2                  // Field $SwitchMap$Color:[I
         9: getstatic     #2                  // Field $SwitchMap$Color:[I
        12: getstatic     #3                  // Field Color.RED:LColor;
        15: invokevirtual #4                  // Method Color.ordinal:()I
        18: iconst_1
        19: iastore
        20: goto          24
        23: astore_0
        24: getstatic     #2                  // Field $SwitchMap$Color:[I
        27: getstatic     #6                  // Field Color.BLUE:LColor;
        30: invokevirtual #4                  // Method Color.ordinal:()I
        33: iconst_2
        34: iastore
        35: goto          39
        38: astore_0
        39: getstatic     #2                  // Field $SwitchMap$Color:[I
        42: getstatic     #7                  // Field Color.YELLOW:LColor;
        45: invokevirtual #4                  // Method Color.ordinal:()I
        48: iconst_3
        49: iastore
        50: goto          54
        53: astore_0
        54: return
}
SourceFile: "MySwitchTest.java"
EnclosingMethod: #22.#0                 // MySwitchTest
InnerClasses:
     static #8; //class MySwitchTest$1

字节码里省略了常量池的内容,以及静态块static{}的一些属性,我们从上依次分析一些重要的内容。

4.1

class MySwitchTest$1
  minor version: 0
  major version: 52
  flags: ACC_SUPER, ACC_SYNTHETIC

major version: 52表示生成这段字节码的jdk版本是8。flags:acc_synthentic表示这个类是编译器自动生成的。

4.2

  static final int[] $SwitchMap$Color;
    descriptor: [I
    flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC

从这一段很容易看出,MySwitchTest$1有一个字段int[] $SwitchMap$Color,flags:ACC_SYNTHETIC 表示该字段是编译器自动生成的。

4.3

static {};
    descriptor: ()V
    flags: ACC_STATIC
    Code:
      stack=3, locals=1, args_size=0
         0: invokestatic  #1                  // Method Color.values:()[LColor;
         3: arraylength
         4: newarray       int
         6: putstatic     #2                  // Field $SwitchMap$Color:[I
         9: getstatic     #2                  // Field $SwitchMap$Color:[I
        12: getstatic     #3                  // Field Color.RED:LColor;
        15: invokevirtual #4                  // Method Color.ordinal:()I
        18: iconst_1
        19: iastore
        20: goto          24
        23: astore_0
        24: getstatic     #2                  // Field $SwitchMap$Color:[I
        27: getstatic     #6                  // Field Color.BLUE:LColor;
        30: invokevirtual #4                  // Method Color.ordinal:()I
        33: iconst_2
        34: iastore
        35: goto          39
        38: astore_0
        39: getstatic     #2                  // Field $SwitchMap$Color:[I
        42: getstatic     #7                  // Field Color.YELLOW:LColor;
        45: invokevirtual #4                  // Method Color.ordinal:()I
        48: iconst_3
        49: iastore
        50: goto          54
        53: astore_0
        54: return

MySwitchTest$1最重要的部分就是静态块static{},指令0~6,调用生成一个int型数组,数组长度和Color.values()的长度一致,并将数组赋值给$SwitchMap$Color字段。指令9~20,调用第一个case的值RED的ordinal(),返回值0,以0为下标,找到数组$SwitchMap$Color的下标为0的元素,赋值为1。指令23,异常处理代码,暂时忽略,后面专门讲解。指令24~35,逻辑与指令9~20相同,为数组$SwitchMap$Color下标为1的元素,赋值为2。指令38,异常处理代码,暂时忽略,后面专门讲解。指令39~50,逻辑与指令9~20相同,为数组$SwitchMap$Color下标为2的元素,赋值为3。指令53,异常处理代码,暂时忽略,后面专门讲解。指令54,return,方法结束。至此,MySwitchTest$1的值为{1, 2, 3}

5.了解了MySwitchTest$1的字节码逻辑之后,我们来看MySwitchTest的chooseColor()方法的字节码。

 public int chooseColor(Color);
    descriptor: (LColor;)I
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=2
         0: getstatic     #2                  // Field MySwitchTest$1.$SwitchMap$Color:[I
         3: aload_1
         4: invokevirtual #3                  // Method Color.ordinal:()I
         7: iaload
         8: tableswitch   { // 1 to 3
                       1: 36
                       2: 38
                       3: 41
                 default: 44
            }
        36: iconst_5
        37: ireturn
        38: bipush        6
        40: ireturn
        41: bipush        7
        43: ireturn
        44: bipush        8
        46: ireturn

指令0~7,获取MySwitchTest$1的字段,由上一节的分析可知该字段值为{1, 2, 3},调用参数Color的ordinal()方法,并以ordinal()方法的返回值为下标,获取MySwitchTest$1.$SwitchMap$Color的元素。由此可知,当参数为RED时,获取的元素为1,当参数BLUE时,获取到的元素为2,当参数YELLOW时,获取到的元素为3,这与上一节根据RED、BLUE、YELLOW元素生成MySwitchTest$1.$SwitchMap$Color的值的逻辑相同。指令8,根据switch生成的指令tableswitch,但是仔细观察case的值并不是RED,BLUE,YELLOW了,而是变成了1,2,3,这也和上一节根据RED、BLUE、YELLOW元素生成MySwitchTest$1.$SwitchMap$Color的值的逻辑相同。指令36~46,根据不同的case值返回不同的结果。

6.总结:由第4节的分析和第5节的分析,可知当switch的变量为Enum时,编译器会生成一个内部类并在该内部类存储一个整型数组,在数组的下标为Enum.ordinal()的位置处存放元素Enum.ordinal()+1。在switch匹配时,也会以枚举变量的ordinal()值为下表,从内部类数组中获取元素作为匹配值,而且switch的case值也是内部类数组值相同。另外,如果想了解switch的字节码,可以参考字节码层面理解Switch,如果想了解Enum的字节码,可以参考字节码层面理解java枚举Enum

 

 

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值