博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java语法糖的编译结果分析(二)
阅读量:5945 次
发布时间:2019-06-19

本文共 17216 字,大约阅读时间需要 57 分钟。

语法糖(Syntactic Sugar)的出现是为了降低我们编写某些代码时陷入的重复或繁琐,这使得我们使用语法糖后可以写出简明而优雅的代码。在Java中不加工的语法糖代码运行时可不会被虚拟机接受,因此编译器为了让这些含有语法糖的代码正常工作其实需要对这些代码进行加工,经过编译器在生成class字节码的阶段完成解语法糖(desugar)的过程,那么这些语法糖最终究竟被编译成了什么呢,在这里列举了如下的一些Java典型的语法糖,结合实例和它们的编译结果分析一下。本文为本系列第二篇。

枚举类

枚举在编译后会变成一个特殊的final类,因此枚举类型是名副其实的不可变类,我们通过下面最简单的例子来仔细分析一下:

源码:

enum COLOR {    RED,    BLUE,    GREEN}

使用这个枚举的时候我们可以发现有valueOf(String)values()这样的方法可以用,因此不难猜测编译器会添加一些未在源码中出现的其他增强二进制字节码,可以看一下具体的字节码:

final class COLOR extends java.lang.Enum
minor version: 0 major version: 52 flags: ACC_FINAL, ACC_SUPER, ACC_ENUM... { public static final COLOR RED; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR BLUE; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR GREEN; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static COLOR[] values(); descriptor: ()[LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LCOLOR; 3: invokevirtual #2 // Method "[LCOLOR;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LCOLOR;" 9: areturn public static COLOR valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class COLOR 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class COLOR 9: areturn static {}; descriptor: ()V flags: ACC_STATIC Code: stack=4, locals=0, args_size=0 0: new #4 // class COLOR 3: dup 4: ldc #7 // String RED 6: iconst_0 7: invokespecial #8 // Method "
":(Ljava/lang/String;I)V 10: putstatic #9 // Field RED:LCOLOR; 13: new #4 // class COLOR 16: dup 17: ldc #10 // String BLUE 19: iconst_1 20: invokespecial #8 // Method "
":(Ljava/lang/String;I)V 23: putstatic #11 // Field BLUE:LCOLOR; 26: new #4 // class COLOR 29: dup 30: ldc #12 // String GREEN 32: iconst_2 33: invokespecial #8 // Method "
":(Ljava/lang/String;I)V 36: putstatic #13 // Field GREEN:LCOLOR; 39: iconst_3 40: anewarray #4 // class COLOR 43: dup 44: iconst_0 45: getstatic #9 // Field RED:LCOLOR; 48: aastore 49: dup 50: iconst_1 51: getstatic #11 // Field BLUE:LCOLOR; 54: aastore 55: dup 56: iconst_2 57: getstatic #13 // Field GREEN:LCOLOR; 60: aastore 61: putstatic #1 // Field $VALUES:[LCOLOR; 64: return}Signature: #32 // Ljava/lang/Enum
;

这段字节码可以证实出上面的猜测,确实会有额外的二进制字节码被添加了,枚举类会被编译成为Ljava/lang/Enum的子类COLOR,而枚举类型中的枚举项会被编译成为COLOR类的常量字段,而且COLOR内部还会维护一个数组来保存这些常量字段,并进而添加valueOf(String)values()来访问这个数组。因此,对应地我们可以翻译这段二进制字节码为这样的代码:

final class COLOR extends Enum
{ private static final COLOR RED; private static final COLOR BLUE; private static final COLOR GREEN; private static final COLOR[] $VALUES; static { RED = new COLOR("RED", 0); BLUE = new COLOR("BLUE", 1); GREEN = new COLOR("GREEN", 2); COLOR[] $COLOR_ARRAY = new COLOR[3]; $COLOR_ARRAY[0] = RED; $COLOR_ARRAY[1] = BLUE; $COLOR_ARRAY[2] = GREEN; $VALUES = $COLOR_ARRAY; } private COLOR(String color, int ordinal) { super(color, ordinal); } public static COLOR[] values() { return $VALUES.clone(); } public static COLOR valueOf(String color) { return Enum.valueOf(COLOR.class, color); }}

注意,这段代码并不能通过编译,因为源码这一层是不允许直接继承Ljava/lang/Enum的,这个继承过程只允许在编译器内部解语法糖的过程中被编译器添加,添加之后的类才会有ACC_ENUM的访问标识符。

我们可以看到的是在Ljava/lang/Enum内部实际上有nameordinal常量来标识一个枚举项,name会由枚举项名来设置,而ordinal是枚举项序号,由枚举项排列顺序决定。

我们再来看一下带有字段的枚举项编译后的效果。

源码:

enum COLOR {    RED(0),    BLUE(1),    GREEN(2);    int code;    COLOR(int code) {        this.code = code;    }}

编译后的字节码:

final class COLOR extends java.lang.Enum
minor version: 0 major version: 52 flags: ACC_FINAL, ACC_SUPER, ACC_ENUM...{ public static final COLOR RED; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR BLUE; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM public static final COLOR GREEN; descriptor: LCOLOR; flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL, ACC_ENUM int code; descriptor: I flags: public static COLOR[] values(); descriptor: ()[LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=1, locals=0, args_size=0 0: getstatic #1 // Field $VALUES:[LCOLOR; 3: invokevirtual #2 // Method "[LCOLOR;".clone:()Ljava/lang/Object; 6: checkcast #3 // class "[LCOLOR;" 9: areturn LineNumberTable: line 1: 0 public static COLOR valueOf(java.lang.String); descriptor: (Ljava/lang/String;)LCOLOR; flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=1, args_size=1 0: ldc #4 // class COLOR 2: aload_0 3: invokestatic #5 // Method java/lang/Enum.valueOf:(Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Enum; 6: checkcast #4 // class COLOR 9: areturn LineNumberTable: line 1: 0 static {}; descriptor: ()V flags: ACC_STATIC Code: stack=5, locals=0, args_size=0 0: new #4 // class COLOR 3: dup 4: ldc #8 // String RED 6: iconst_0 7: iconst_0 8: invokespecial #9 // Method "
":(Ljava/lang/String;II)V 11: putstatic #10 // Field RED:LCOLOR; 14: new #4 // class COLOR 17: dup 18: ldc #11 // String BLUE 20: iconst_1 21: iconst_1 22: invokespecial #9 // Method "
":(Ljava/lang/String;II)V 25: putstatic #12 // Field BLUE:LCOLOR; 28: new #4 // class COLOR 31: dup 32: ldc #13 // String GREEN 34: iconst_2 35: iconst_2 36: invokespecial #9 // Method "
":(Ljava/lang/String;II)V 39: putstatic #14 // Field GREEN:LCOLOR; 42: iconst_3 43: anewarray #4 // class COLOR 46: dup 47: iconst_0 48: getstatic #10 // Field RED:LCOLOR; 51: aastore 52: dup 53: iconst_1 54: getstatic #12 // Field BLUE:LCOLOR; 57: aastore 58: dup 59: iconst_2 60: getstatic #14 // Field GREEN:LCOLOR; 63: aastore 64: putstatic #1 // Field $VALUES:[LCOLOR; 67: return LineNumberTable: line 2: 0 line 3: 14 line 4: 28 line 1: 42}Signature: #36 // Ljava/lang/Enum
;

用java源码翻译下上面的结果:

final class COLOR extends Enum
{ private static final COLOR RED; private static final COLOR BLUE; private static final COLOR GREEN; int code; private static final COLOR[] $VALUES; static { RED = new COLOR("RED", 0, 0); BLUE = new COLOR("BLUE", 1, 1); GREEN = new COLOR("GREEN", 2, 2); COLOR[] $COLOR_ARRAY = new COLOR[3]; $COLOR_ARRAY[0] = RED; $COLOR_ARRAY[1] = BLUE; $COLOR_ARRAY[2] = GREEN; $VALUES = $COLOR_ARRAY; } private COLOR(String color, int ordinal, int code) { super(color, ordinal); this.code = code; } public static COLOR[] values() { return $VALUES.clone(); } public static COLOR valueOf(String color) { return Enum.valueOf(COLOR.class, color); }}

其实有了之前的基础很容易看出来,新增加的code字段最终只是变成了编译器生成的COLOR类的一个字段,唯一的变化就是编译出的初始化方法也会增加为这个字段而添加的参数。

断言

java 1.4引入的断言,使用关键字assert来判断一个条件是否为true,通过如下的源码来分析一下:

class Main {    public static void main(String[] args) {        String judge = "yes";        assert "no".equals(judge);    }}

断言在运行时默认是关闭的,我们可以通过运行时打开断言来启用:java -ea Main

at Main.main(Main.java:4)

那么我们来看一下编译后的字节码:

{  static final boolean $assertionsDisabled;    descriptor: Z    flags: ACC_STATIC, ACC_FINAL, ACC_SYNTHETIC  Main();    descriptor: ()V    flags:    Code:      stack=1, locals=1, args_size=1         0: aload_0         1: invokespecial #1                  // Method java/lang/Object."
":()V 4: return LineNumberTable: line 1: 0 public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V flags: ACC_PUBLIC, ACC_STATIC Code: stack=2, locals=2, args_size=1 0: ldc #2 // String yes 2: astore_1 3: getstatic #3 // Field $assertionsDisabled:Z 6: ifne 26 9: ldc #4 // String no 11: aload_1 12: invokevirtual #5 // Method java/lang/String.equals:(Ljava/lang/Object;)Z 15: ifne 26 18: new #6 // class java/lang/AssertionError 21: dup 22: invokespecial #7 // Method java/lang/AssertionError."
":()V 25: athrow 26: return StackMapTable: number_of_entries = 1 frame_type = 252 /* append */ offset_delta = 26 locals = [ class java/lang/String ] static {}; descriptor: ()V flags: ACC_STATIC Code: stack=1, locals=0, args_size=0 0: ldc #8 // class Main 2: invokevirtual #9 // Method java/lang/Class.desiredAssertionStatus:()Z 5: ifne 12 8: iconst_1 9: goto 13 12: iconst_0 13: putstatic #3 // Field $assertionsDisabled:Z 16: return StackMapTable: number_of_entries = 2 frame_type = 12 /* same */ frame_type = 64 /* same_locals_1_stack_item */ stack = [ int ]}

可以发现编译器为Main类添加了字段$assertionsDisabled,此字段即是启用断言的关键。在运行时加入启用断言的-ea会使得类初始化时Class.desiredAssertionStatus为真,进而字段$assertionsDisabled为真,这个逻辑在上述的字节码中可以看出。在断言的地方,如果条件为真则会正常返回,如果条件为false则会抛出java/lang/AssertionError错误导致程序终止。

用java源码翻译下上面的结果:

class Main {    private static final boolean $assertionsDisabled;    static {        if (Main.class.desiredAssertionStatus()) {            $assertionsDisabled = true;        } else {            $assertionsDisabled = false;        }    }    public static void main(String[] args) {        if($assertionsDisabled) {            if (!"no".equals("yes")) {                throw new AssertionError();            }        }    }}

switch处理枚举和字符串

我们先来看看在java 1.7以前就可以使用switch的类型在字节码层是如何工作的,这里以int类型为例:

class Main {    public static void main(String[] args) {        int a = 1;        switch (a) {            case 0:                System.out.println("0");                break;            case 2:                System.out.println("1");                break;            case 8:                System.out.println("3");                break;            default:                break;        }    }}

编译后的字节码:

public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=2, locals=2, args_size=1         0: iconst_1         1: istore_1         2: iload_1         3: lookupswitch  { // 3                       0: 36                       2: 47                       8: 58                 default: 69            }        36: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;        39: ldc           #3                  // String 0        41: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V        44: goto          69        47: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;        50: ldc           #5                  // String 1        52: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V        55: goto          69        58: getstatic     #2                  // Field java/lang/System.out:Ljava/io/PrintStream;        61: ldc           #6                  // String 3        63: invokevirtual #4                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V        66: goto          69        69: return}

这里是用的字节码命令lookupswitch适用于判断switch的跳转语句的,即如果switch 0跳转到26行、switch 2跳转到47行、switch 8跳转到58行、其他跳转到69行。

受限于lookupswitch判断的条件的类型,在java 1.7以前是无法对非32位数字类型的类型做判断的,而java 1.7以后通过语法糖的解析实现了字符串的switch分支判断,可以想到的是,在不改变lookupswitch的能力的情况下,编译器会将字符串转换为32位数字。我们写这样的例子来分析下:

class Main {    public static void main(String[] args) {        String a = args[0];        switch (a) {            case "a":                System.out.println("a");                break;            case "b":                System.out.println("b");                break;            default:                break;        }    }}

编译后的结果:

public static void main(java.lang.String[]);    descriptor: ([Ljava/lang/String;)V    flags: ACC_PUBLIC, ACC_STATIC    Code:      stack=2, locals=4, args_size=1         0: aload_0         1: iconst_0         2: aaload         3: astore_1         4: aload_1         5: astore_2         6: iconst_m1         7: istore_3         8: aload_2         9: invokevirtual #2                  // Method java/lang/String.hashCode:()I        12: lookupswitch  { // 2                      97: 40                      98: 54                 default: 65            }        40: aload_2        41: ldc           #3                  // String a        43: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z        46: ifeq          65        49: iconst_0        50: istore_3        51: goto          65        54: aload_2        55: ldc           #5                  // String b        57: invokevirtual #4                  // Method java/lang/String.equals:(Ljava/lang/Object;)Z        60: ifeq          65        63: iconst_1        64: istore_3        65: iload_3        66: lookupswitch  { // 2                       0: 92                       1: 103                 default: 114            }        92: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;        95: ldc           #3                  // String a        97: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V       100: goto          114       103: getstatic     #6                  // Field java/lang/System.out:Ljava/io/PrintStream;       106: ldc           #5                  // String b       108: invokevirtual #7                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V       111: goto          114       114: return      LocalVariableTable:        Start  Length  Slot  Name   Signature            0     115     0  args   [Ljava/lang/String;            4     111     1     a   Ljava/lang/String;}

我们可以发现编译器对要做分支判断的字符串计算了它的hashcode,而这个hashcode是符合lookupswitch要求的32位数字,因此将这个hashcode做lookupswitch分支判断,和switch条件中的"a""b"的hashcode做比较,如果进入了其中某个分支如"a"分支,则在分支中判断"a"和字符串是否相等,如果相等则确定此分支是正确的(只有hashcode相等并不能确定是值相等,hashcode的冲突原理不再展开),接下来再将分支条件直接设置为0、1、2这样的简单条件执行下一轮lookupswitch。我们同样可以用如下java源码翻译下上面的结果:

class Main {    public static void main(String[] args) {        String param = args[0];        int hashcode = param.hashCode();        final int condition_a = 97; //"a".hashCode()        final int condition_b = 98; //"b".hashCode();        int hashcodeSwitchResult = -1;        switch (hashcode) {            case condition_a:                if("a".equals(param)){                    hashcodeSwitchResult = 0;                }                break;            case condition_b:                if("b".equals(param)){                    hashcodeSwitchResult = 1;                }                break;            default:                break;        }        switch (hashcodeSwitchResult) {            case 0:                System.out.println("a");                break;            case 1:                System.out.println("b");                break;            default:                break;        }    }}

转载地址:http://kuzxx.baihongyu.com/

你可能感兴趣的文章
ListView之二。
查看>>
ubuntu无限卡在logo界面
查看>>
【百度地图API】JS版本的常见问题
查看>>
【高德地图API】从零开始学高德JS API(三)覆盖物——标注|折线|多边形|信息窗口|聚合marker|麻点图|图片覆盖物...
查看>>
P1197 [JSOI2008]星球大战
查看>>
课后作业:字符串加密
查看>>
c# byte char string转换
查看>>
图的实现(邻接链表C#)
查看>>
一个页面上有大量的图片(大型电商网站),加载很慢,你有哪些方法优化这些图片的加载,给用户更好的体验。...
查看>>
asyncio之Coroutines,Tasks and Future
查看>>
JS-完美运动框架(封装)
查看>>
美容院会籍管理,看着简单,其实很复杂
查看>>
Two Sum(leetcode1)
查看>>
浪潮各机型管理芯片BMC IP(智能平台管理接口)设置
查看>>
JSP是不是Java发展史上的一大败笔?
查看>>
【CF671D】 Roads in Yusland(对偶问题,左偏树)
查看>>
反编译sencha toucha打包的apk文件,修改应用名称支持中文以及去除应用标题栏
查看>>
Win32 API
查看>>
虚函数(1)
查看>>
动态使用webservice,以及含有ref类型的参数的问题
查看>>