21.3 Java 10的局部变量类型推断
最初在Java语言中,如果你要引入一个变量或者方法,必须同时给出它的类型。比如下面这个例子:
double convertUSDToGBP(double money) { ExchangeRate e = ...; }
convertUSDToGBP包含三种类型,方法声明分别指定了它的返回结果、接受的参数money,以及局部变量e的类型。随着时间的推移,这种限制渐渐放宽了,主要体现在两个方面。首先,你可以在表达式中省略泛型参数的类型,由程序依据上下文进行判断。比如下面这个例子:
Map<String, List<String>> myMap = new HashMap<String, List<String>>();
从Java 7开始,这行代码可以缩略成下面这种书写方式:
Map<String, List<String>> myMap = new HashMap<>();
其次,基于同样的思想,我们可以利用上下文来传递表达式中的变量类型,比如下面这个Lambda表达式:
Function<Integer, Boolean> p = (Integer x) -> booleanExpression;
省略变量类型之后,可以缩写为:
Function<Integer, Boolean> p = x -> booleanExpression;
这两个例子,编译器都需要依据上下文推断省略的变量类型。
如果类型只有唯一的标识符,那么采用类型推断能带来很多好处,其中最主要的优势之一是,当用一种类型替换另一种类型后,不用重新编辑修改代码了。不过,随着类型数量的增加,处理由更高级的泛型类型参数化的泛型时,使用类型推断能帮助提升代码的可读性。1Scala和C#语言允许使用(受限)关键字var替换本地初始化的变量(local-variable-initialized)类型;编译器会在右边填充恰当的类型。我们之前用Java语法声明的myMap采用var之后可以改写如下:
1类型推断必须很直观,这一点非常重要。如果类型只存在一种可能性,或者只有一种容易文档化的方式,那么采用类型推断重新创建用户忽略的类型,其效果是最好的。如果系统推断的类型与用户期望的类型不一致,就会出现问题。一个设计良好的类型推断系统,如果发现它可能产生两个不兼容的类型时,就应该抛出一个异常。采用启发式的方法选择类型可能导致类型推断随机选择错误类型的现象。
var myMap = new HashMap<String, List<String>>();
这种思想被称作局部变量推断,其会在Java 10中提供支持。
然而,人们对这种技术也存在着一些疑虑。举个例子,假设Car类是Vehicle类的子类,下面的这个声明
var x = new Car();
执行隐式转换的时候,到底是该把x声明为Car类型还是Vehicle类型呢?还是说应该将其转换为Object类型?对这种情况,一个简单的解释是,缺失的类型可以由初始化器来决定(这里对应的就是Car类型)。Java 10对这种情况定义了更加清晰的规范。此外,还有一点需要特别提一下,var不能用于没有初始化器的场景。
