文档章节

JVM—编译器之解语法糖

Ambitor
 Ambitor
发布于 2015/12/11 13:33
字数 2115
阅读 118
收藏 1

JVM—编译器之解语法糖

众所周知JAVA的编译器包含两种:

  • 前端编译器(编译器前端):指的是把.java源文件编译成.class字节码文件,而我们平时大部分人所挂在嘴边或被知道也就是指的前端编译器

  • 运行时编译器(JIT:Just in Time Compile):指的是把.class字节码转换成机器码的过程,编译器的绝大部分优化是在这个时候做的,原因是JVM是一个平台,在运行时做优化可以针对所有在JVM上运行的编程语言(如果这个时候你还认为JAVA语言理所应当可以运行在JVM上的话,你可能没有理解JVM是一个平台的意义)

而本文,笔者将主要赘述跟我们编码息息相关的前端编译器,而本文后续所指的编译器如无例外说明,全部指的是前端编译器。因为从java编译到class字节码是一个繁琐且复杂的过程,所以本文中笔者不会讲述解析与填充符号表语法树注解处理器处理过程等复杂且枯燥的内容,如文中有涉及会提前介绍。

语法糖

语法糖:也译为糖衣语法,是由英国计算机科学家彼得·约翰·兰达(Peter J. Landin)发明的一个术语,指计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。通常来说使用语法糖能够增加程序的可读性,从而减少程序代码出错的机会。

JAVA属于一种“低糖语法”的语言(相对C#及其他JVM语言),使用JAVA语言编程的过程中语法糖随处可见,如:泛型自动包装自动拆箱内部类变长参数等,在虚拟机运行时并不支持这些语法,所以在编译器会把这些语法编译成JVM运行时能读懂的class字节码,这个过程叫:解语法糖,接下来我们来分析语法糖在JAVA中的使用

自动包装/拆箱

我们先来看下比较简单的包装和拆箱,请读者仔细分析下面程序,得出结果:

public class SugarTest {

public static void main(String[] args) {
    Integer a = 1;
    Integer b = 2;
    Integer c = 3;
    Integer d = 3;
    Integer e = 128;
    Integer f = 128;
    Long h = 3L;

    System.out.println(c == d);             //问题1 想知道答案?
    System.out.println(e == f);             //问题2   不告诉你!
    System.out.println(c == (a + b));       //问题3   就不告诉你!
    System.out.println(e.equals(f));        //问题4   自己想啊...
    System.out.println(h == (a + b));       //问题5   好吧,我下面会说的...
   }
}

正确的结果是:true 、false 、true 、true 、true

我们都知道Java会自动包装和拆箱,接下来我们去看看Java是怎么实现自动包装和拆箱的,前面已经介绍过自动包装和拆箱其实是一种Java的语法糖,编译器会在编译成class的时候解语法糖,那么我们反编译出java的class文件就知道编译器最后把语法糖解成什么了,下面是反编译后的代码

public class SugarTest {
    public SugarTest() {//编译器为我们加的默认构造函数
    }

    public static void main(String[] args) {
        Integer a = Integer.valueOf(1); 
        Integer b = Integer.valueOf(2);
        Integer c = Integer.valueOf(3);
        Integer d = Integer.valueOf(3);
        Integer e = Integer.valueOf(128);
        Integer f = Integer.valueOf(128);
        Long h = Long.valueOf(3L);
        System.out.println(c == d);
        System.out.println(e == f);
        System.out.println(c.intValue() == a.intValue() + b.intValue());
        System.out.println(e.equals(f));
        System.out.println(h.longValue() == (long)(a.intValue() + b.intValue()));
    }
}

很容易看出来,当我们Integer a = 1的时候实际上编译器会帮我们编译成Integer a = Integer.valueOf(1);而当Integer遇到算数运算的时候编译器会帮我们编译成调用initValue()拆箱去计算,除此之外还有什么时候会自动拆箱?当然Integer的equals()不属于编译器帮我们自动拆箱的,而是方法里面使用的是.intValue()去比较的,这里就不说了,有兴趣朋友可以自己去看源码。

到这里为止,我相信很多读者最大的疑问是在于问题1和问题2的结果。 我们再来看为什么 c==d 为true e==f为fasle?关于这个问题 我们先来看看Integer的valueOf()代码

public static Integer valueOf(int i) {
    assert IntegerCache.high >= 127;
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

事实上当在 IntegerCache.low =< i <= IntegerCache.high区间的时候返回的是IntegerCache.cache[i + (-IntegerCache.low)] cache是一个Integer数组,而超出这个范围valueOf将return一个new出来的新Integer,所以用 e==f 返回的是false,而这个IntegerCache.low和IntegerCache.high默认分别是 -128和127,也就是说当Integer的值在-128和127之间的时候编译器或者说Java会自动包装和拆箱 ,否则用 == 比较的时候返回的是false,所以这个地方大家在编码的时候就需要注意了!而这个low和high的范围我们可以通过 -XX:AutoBoxCacheMax=设置,下面我们看下JDK源码对IntegerCache的注释

/**
 * Cache to support the object identity semantics of autoboxing for values between
 * -128 and 127 (inclusive) as required by JLS.
 *
 * The cache is initialized on first usage.  The size of the cache
 * may be controlled by the -XX:AutoBoxCacheMax=<size> option.
 * During VM initialization, java.lang.Integer.IntegerCache.high property
 * may be set and saved in the private system properties in the
 * sun.misc.VM class.
 */

泛型语法糖

看完了自动拆箱/包装,我们再来看下Java泛型,有意思的是Java泛型和C#不一样的是,C#的泛型是真的底层实现,对于C#来说List<String>和List<User>是两种不同的类型,而在Java他们被认为是一种类型,看下面的例子:

public void updateUser(List<String> ids){}

public void updateUser(List<User> users){}

上述代码在Java中会编译失败,提示已经有相同的方法存在,这是因为Java编译器会把泛型在编译成class的时候解语法糖,去掉泛型的类型(也就是你听说过的泛型擦除),所以List<'String>和List<'User>是两种相同的类型,然后我们再来看下反编译后的泛型代码:

public static void updateUser(List<SugarTest.User> users) {}

这个时候奇怪的是class反编译过来的代码却保存着泛型User,而想想平时开发中我们也可以用反射获取泛型的类型,这跟上述的泛型擦除是否相矛盾?事实上获取泛型的类型在有时候是必须的,所以编译器在class文件中留有一个字节保存着泛型的原始类型,也就是User。

如今,我们已经不知道Java为什么使用语法糖去完善泛型语法,或许是因为Java有历史遗留问题,毕竟泛型是1.5后推出的。但这种形式的泛型在我们开发中还是会带来一些不便,最简单的就是上述的例子。

Switch支持String类型

JDK1.7推出了Switch语法支持String类型,这给使用Switch开发带来了便利,那我们就来看下Java是怎么实现的呢?

//源码
switch ("xxxx") {
        case "xxxx": {
            System.out.println("xxx");
            break;
        }
    }

//反编译后的代码   
String var8 = "xxxx";
byte var9 = -1;
switch(var8.hashCode()) {
case 3694080:
    if(var8.equals("xxxx")) {
        var9 = 0;
    }
default:
    switch(var9) {
    case 0:
        System.out.println("xxx");
    default:
    }
}

有了前面的分析方法,我们可以很直观的看出来编译器把String的Switch语句编译成使用String的HashCode()去做的,也就是说事实上运行时虚拟机仍然只支持switch(int)

变长数组

变长数组也是java语法糖的实现之一,我们常常会看到这种下法:

public void printSome(String... str){}

printSome("1","2","3");

看看编译器帮我们编译成String数组

printSome(new String[]{"1", "2", "3"});

总结

以上就是我们看到在Java中的语法糖,除了文中列举的语法,还包括内部类枚举等一系列语法,尤其是Integer、Long(和Integer一样)等数值的自动包装和拆箱在用的过程中一定要注意。


转载请注明出处:http://my.oschina.net/ambitor/blog/542789

© 著作权归作者所有

Ambitor
粉丝 74
博文 33
码字总数 33300
作品 0
深圳
技术主管
私信 提问
不了解这12个语法糖,别说你会Java!

本文从 Java 编译原理角度,深入字节码及 class 文件,抽丝剥茧,了解 Java 中的语法糖原理及用法,帮助大家在学会如何使用 Java 语法糖的同时,了解这些语法糖背后的原理 语法糖 语法糖(S...

编程SHA
05/16
103
2
JDK7 "Project Coin" 语法新特性尝鲜

JDK7在20110728已经发布,分别在jvm、语法、国际化、jdbc、异步api等多方面有增强或新特性引入;其中java本身的语法增强似乎并不如想象中的那么令人激动...从代表着jdk7语法变更的"Project ...

鉴客
2012/06/28
318
0
06-《深度拆解JVM》之JVM是如何处理异常的?

一、问题引入 今天我们来讨论下 Java 虚拟机的异常处理。众所周知,异常处理的两大组成要素是抛出异常和捕获异常。这两大要素共同实现程序控制流的非正常转移。 抛出异常可分为显式和隐式两种...

飞鱼说编程
2018/09/28
61
2
16-《深度拆解JVM》之Java语法糖与Java编译器

在前面的文章中,我们多次提到了 Java 语法和 Java 字节码的差异之处。这些差异之处都是通过 Java 编译器来协调的。今天我们便来列举一下 Java 编译器的协调工作。 一、自动装箱与自动拆箱 ...

飞鱼说编程
2018/11/09
10
1
读《深入理解Java虚拟机》- 笔记08

《深入理解Java虚拟机:JVM高级特性与最佳实践》第2版 第10章 早期(编译期)优化 59. 语法糖 在计算机语言中添加某种语法,对语言的功能没有影响,但是方便开发人员使用。 泛型是一种语法糖...

阿历Ali
2018/08/18
0
0

没有更多内容

加载失败,请刷新页面

加载更多

Spring Boot 2 实战:使用 Spring Boot Admin 监控你的应用

1. 前言 生产上对 Web 应用 的监控是十分必要的。我们可以近乎实时来对应用的健康、性能等其他指标进行监控来及时应对一些突发情况。避免一些故障的发生。对于 Spring Boot 应用来说我们可以...

码农小胖哥
32分钟前
3
0
ZetCode 教程翻译计划正式启动 | ApacheCN

原文:ZetCode 协议:CC BY-NC-SA 4.0 欢迎任何人参与和完善:一个人可以走的很快,但是一群人却可以走的更远。 ApacheCN 学习资源 贡献指南 本项目需要校对,欢迎大家提交 Pull Request。 ...

ApacheCN_飞龙
43分钟前
3
0
CSS定位

CSS定位 relative相对定位 absolute绝对定位 fixed和sticky及zIndex relative相对定位 position特性:css position属性用于指定一个元素在文档中的定位方式。top、right、bottom、left属性则...

studywin
51分钟前
6
0
从零基础到拿到网易Java实习offer,我做对了哪些事

作为一个非科班小白,我在读研期间基本是自学Java,从一开始几乎零基础,只有一点点数据结构和Java方面的基础,到最终获得网易游戏的Java实习offer,我大概用了半年左右的时间。本文将会讲到...

Java技术江湖
昨天
5
0
程序性能checklist

程序性能checklist

Moks角木
昨天
7
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部