2.5 类型推断

某些情况下,用户需要手动指明类型,建议大家根据自己或项目组的习惯,采用让代码最便于阅读的方法。有时省略类型信息可以减少干扰,更易弄清状况;而有时却需要类型信息帮助理解代码。经验证发现,一开始类型信息是有用的,但随后可以只在真正需要时才加上类型信息。下面将介绍一些简单的规则,来帮助确认是否需要手动声明参数类型。

Lambda表达式中的类型推断,实际上是Java 7就引入的目标类型推断的扩展。读者可能已经知道Java 7中的菱形操作符,它可使javac推断出泛型参数的类型。参见例2-9。

例2-9 使用菱形操作符,根据变量类型做推断

  1. Map<String, Integer> oldWordCounts = new HashMap<String, Integer>();➊
  2. Map<String, Integer> diamondWordCounts = new HashMap<>();➋

我们为变量oldWordCounts➊明确指定了泛型的类型,而变量diamondWordCounts➋则使用了菱形操作符。不用明确声明泛型类型,编译器就可以自己推断出来,这就是它的神奇之处!

当然,这并不是什么魔法,根据变量diamondWordCounts➋的类型可以推断出HashMap的泛型类型,但用户仍需要声明变量的泛型类型。

如果将构造函数直接传递给一个方法,也可根据方法签名来推断类型。在例2-10中,我们传入了HashMap,根据方法签名已经可以推断出泛型的类型。

例2-10 使用菱形操作符,根据方法签名做推断

  1. useHashmap(new HashMap<>());
  2. ...
  3. private void useHashmap(Map<String, String> values);

Java 7中程序员可省略构造函数的泛型类型,Java 8更进一步,程序员可省略Lambda表达式中的所有参数类型。再强调一次,这并不是魔法,javac根据Lambda表达式上下文信息就能推断出参数的正确类型。程序依然要经过类型检查来保证运行的安全性,但不用再显式声明类型罢了。这就是所谓的类型推断

2.5 类型推断 - 图1Java 8中对类型推断系统的改善值得一提。上面的例子将new HashMap<>()传给useHashmap方法,即使编译器拥有足够的信息,也无法在Java 7中通过编译。

接下来将通过举例来详细分析类型推断。

例2-11和例2-12都将变量赋给一个函数接口,这样便于理解。第一个例子(例2-11)使用Lambda表达式检测一个Integer是否大于5。这实际上是一个Predicate——用来判断真假的函数接口。

例2-11 类型推断

  1. Predicate<Integer> atLeast5 = x -> x > 5;

Predicate也是一个Lambda表达式,和前文中ActionListener不同的是,它还返回一个值。在例2-11中,表达式x > 5是Lambda表达式的主体。这样的情况下,返回值就是Lambda表达式主体的值。

例2-12 Predicate接口的源码,接受一个对象,返回一个布尔值

  1. public interface Predicate<T> {
  2. boolean test(T t);
  3. }

从例2-12中可以看出,Predicate只有一个泛型类型的参数,Integer用于其中。Lambda表达式实现了Predicate接口,因此它的单一参数被推断为Integer类型。javac还可检查Lambda表达式的返回值是不是boolean,这正是Predicate方法的返回类型(如图2-2)。

2.5 类型推断 - 图2

图2-2:Predicate接口图示,接受一个对象,返回一个布尔值

例2-13是一个略显复杂的函数接口:BinaryOperator。该接口接受两个参数,返回一个值,参数和值的类型均相同。实例中所用的类型是Long

例2-13 略显复杂的类型推断

  1. BinaryOperator<Long> addLongs = (x, y) -> x + y;

类型推断系统相当智能,但若信息不够,类型推断系统也无能为力。类型系统不会漫无边际地瞎猜,而会中止操作并报告编译错误,寻求帮助。比如,如果我们删掉例2-13中的某些类型信息,就会得到例2-14所示的代码。

例2-14 没有泛型,代码则通不过编译

  1. BinaryOperator add = (x, y) -> x + y;

编译器给出的报错信息如下:

  1. Operator '& #x002B;' cannot be applied to java.lang.Object, java.lang.Object.

报错信息让人一头雾水,到底怎么回事?BinaryOperator毕竟是一个具有泛型参数的函数接口,该类型既是参数xy的类型,也是返回值的类型。上面的例子中并没有给出变量add的任何泛型信息,给出的正是原始类型的定义。因此,编译器认为参数和返回值都是java.lang.Object实例。

4.3节还会讲到类型推断,但就目前来说,掌握以上类型推断的知识就已经足够了。