天道不一定酬所有勤
但是,天道只酬勤
Hollis出品的全套Java面试宝典不来了解一下吗?

我反编译了Java 10的本地变量类型推断

Hollis出品的全套Java面试宝典不来了解一下吗?

北京时间 3 月 21 日,Oracle 官方宣布 Java 10 正式发布。这是 Java 大版本周期变化后的第一个正式发布版本。关于Java 10 ,最值得程序员关注的一个新特性恐怕就是本地变量类型推断(local-variable type inference)了。

Java 10 推出之后,很多文章也随之出来了,告诉我们有哪些特性,告诉我们本地变量类型推断怎么用。但是,知其然,要知其所以然。

Java 10发布之后,我第一时间下载了这个版本的Jdk并安装到我的电脑中,然后写了一段代码,真正的感受一下本地变量推断到底如何。这篇文章简单来谈一下我的感受。

关于本地变量类型推断的用法,我的《Java 10将于本月发布,它会改变你写代码的方式》中有介绍过。主要可以用在以下几个场景中:

public class VarDemo {

    public static void main(String[] args) {
        //初始化局部变量  
        var string = "hollis";
        //初始化局部变量  
        var stringList = new ArrayList<String>();
        stringList.add("hollis");
        stringList.add("chuang");
        stringList.add("weChat:hollis");
        stringList.add("blog:http://www.hollischuang.com");
        //增强for循环的索引
        for (var s : stringList){
            System.out.println(s);
        }
        //传统for循环的局部变量定义
        for (var i = 0; i < stringList.size(); i++){
            System.out.println(stringList.get(i));
        }
    }
}

然后,使用java 10的javac命令进行编译:

/Library/Java/JavaVirtualMachines/jdk-10.jdk/Contents/Home/bin/javac VarDemo.java

生成VarDemo.class文件,我们对VarDemo.class进行反编译。用jad进行反编译得到以下代码:

public class VarDemo
{
    public static void main(String args[])
    {
        String s = "hollis";
        ArrayList arraylist = new ArrayList();
        arraylist.add("hollis");
        arraylist.add("chuang");
        arraylist.add("weChat:hollis");
        arraylist.add("blog:http://www.hollischuang.com");
        String s1;
        for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(s1))
            s1 = (String)iterator.next();

        for(int i = 0; i < arraylist.size(); i++)
            System.out.println((String)arraylist.get(i));

    }
}

这段代码我们就很熟悉了,就是在Java 10之前,没有本地变量类型推断的时候写的代码。代码的对应关系如下:

本地变量类型推断写法 正常写法
var string = "hollis"; String string = "hollis";
var stringList = new ArrayList(); ArrayList stringList = new ArrayList();
for (var s : stringList) for (String s : stringList)
for (var i = 0; i < stringList.size(); i++) for (int i = 0; i < stringList.size(); i++)

ArrayList arraylist = new ArrayList(); 其实是ArrayList<String> stringList = new ArrayList<String>(); 解糖后,类型擦除后的写法。

for(Iterator iterator = arraylist.iterator(); iterator.hasNext(); System.out.println(s1)) 其实是 for (String s : stringList) 这种for循环解糖后的写法。

所以,本地变量类型推断,也是Java 10提供给开发者的语法糖。虽然我们在代码中使用var进行了定义,但是对于虚拟机来说他是不认识这个var的,在java文件编译成class文件的过程中,会进行解糖,使用变量真正的类型来替代var(如使用String string 来替换 var string)。对于虚拟机来说,完全不需要对var做任何兼容性改变,因为他的生命周期在编译阶段就结束了。唯一变化的是编译器在编译过程中需要多增加一个关于var的解糖操作。

感兴趣的同学可以写两段代码,一段使用var,一段不使用var,然后对比下编译后的字节码。你会发现真的是完全一样的。下图是我用diff工具对比的结果。

diff

语法糖(Syntactic Sugar),也称糖衣语法,是由英国计算机学家 Peter.J.Landin 发明的一个术语,指在计算机语言中添加的某种语法,这种语法对语言的功能并没有影响,但是更方便程序员使用。简而言之,语法糖让程序更加简洁,有更高的可读性。

和JavaScript有啥区别

很多人都知道,在JavaScript中,变量的定义就是使用var来声明的。所以,Java 10的本地变量类型推断刚刚一出来,就有人说了,这不就是抄袭JavaScript的吗?这和JS里面的var不是一样吗?

其实,还真的不一样。

首先,JavaScript 是一种弱类型(或称动态类型)语言,即变量的类型是不确定的。你可以在JavaScript中,使用“4”-3这样的语法,他的的结果是数字1,这里字符串和数字做运算了。不信的话,你打开你浏览器的控制台,试一下:

console

但是,Java中虽然可以使用var来声明变量,但是它还是一种强类型的语言。通过上面反编译的代码,我们已经知道,var只是Java给开发者提供的语法糖,最终在编译之后还是要将var定义的对象类型定义成编译器推断出来的类型的。

到底会不会影响可读性

本地变量类型推断最让人诟病的恐怕就是其可读性了,因为在之前,我们定义变量时候要明确指定他的类型,所以在阅读代码的时候只要看其声明的类型就可以知道他的类型了,但是全都使用var之后,那就惨了。毫无疑问,这会损失一部分可读性的。但是,在代码中使用var声明对象同样也带来了很多的好处,如代码更加简洁等。

一个新东西刚刚出来之前,总会有各种不习惯。现在大家就会觉得这东西太影响我阅读代码的效率。就像淘宝商城刚刚改名叫天猫的时候,大家都觉得,这是个什么鬼名字。现在听习惯了,是不是觉得还挺好的。

如果大家都使用了var来声明变量以后,那么变量的名字就更加重要了。那时候大家就会更注重变量起名的可读性。而且,相信不久,各大IDE就会推出智能显示变量的推断类型功能。所以,从各个方面,都能弥补一些不足。

总之,对于本地变量类型推断这一特性,我是比较积极的拥抱的。

最后,再提出一个问题,供大家思考,本地变量类型推断看上去还是挺好用的,而且,既然Java已经决定在新版本中推出他,那么为什么要限制他的用法呢。现在已知的可以使用var声明变量的几个场景就是初始化局部变量、增强for循环的索引和传统for循环的局部变量定义,还有几个场景是不支持这种用法的,如:

方法的参数 构造函数的参数 方法的返回值类型 对象的成员变量 只是定义定义而不初始化

那么,我的问题是,Java为什么做这些限制,考虑是什么?回答的靠谱的同学有奖励哦。免费拉你进入我的知识星球。

赞(3)
如未加特殊说明,此网站文章均为原创,转载必须注明出处。HollisChuang's Blog » 我反编译了Java 10的本地变量类型推断
Hollis出品的全套Java面试宝典不来了解一下吗?

评论 7

  1. #1

    因为 Java 在运行时还是强类型的语言。如果方法的参数 构造函数的参数 方法的返回值类型都放开限制的话,是没有办法在编译器推断出变量类型的,方法的重载也无从谈起,运行时易报类型转换错。强类型语言的有点应该保留。

    念你情7年前 (2018-03-27)回复
  2. #2

    不错不错,过几天面试吹一波!
    [/钱]

    问心无愧7年前 (2018-03-30)回复
  3. #3

    var当然不能全部展开使用,主要还是保证java程序的正确性,简洁,可读性。
    比如一个方法参数搞成了func(var a ) {},那么在这个a变量正式使用之前,没人知道这个a是要干嘛用的,也就无法推断类型了,会使程序难以理解。
    还比如,使用var,可能会使面向接口编程的概念变的混乱,到底是接口呢,还是某个实现类呢?
    话说回来能正常使用var的场景,应该也就是那些能够根据引用返回值来推断的。比如 new XXX,当然可以推断一下是什么类型了。

    问心无愧7年前 (2018-03-30)回复
  4. #4

    要是我我绝不不用var,说到底还是要推断

    Paris7年前 (2018-05-14)回复
  5. #5

    我猜 类型推断还是要根据实例来推断类型的,H大提到的几处地方都是声明,形参而已,没有实例,一个var,完全没办法推断出来

    散场电影6年前 (2018-07-24)回复
  6. #6

    var 是根据赋值的类型进行推导的 如果方法参数用var 则类型就不明确了 还有一个问题 方法的重载 如 f(‘1’) f(1) 这种情况下

    yangrd6年前 (2019-01-26)回复
  7. #7

    猜测可能是因为java的核心-多态与本地类型推断在一定程度上是矛盾的

    厚朴4年前 (2020-06-09)回复

HollisChuang's Blog

联系我关于我