JDK8 的 Lambda 表达式 -- 类型推断

现在我们来看看 JDK8 是怎么对 Lambda 表达式进行类型推断的,Lambda 的实际类型叫做它的目标类型(target type),因为 JDK8 沿承了已有的类型系统,所以象这样的写法:

button.addActionListener((ActionEvent e) -> foo());

addActionListener() 方法接收的是一个 ActionListener 类型的参数,所以这里的 Lambda 表达式 (ActionEvent e) -> foo() 代表的就是一个 ActionListener 实例,编译器是怎么知道这一点的,而且相同的 Lambda 是可以表示不同的类型的,见:

Callable<String> c = () -> "done";     //这个 () -> "done" 是 Callable<String> 类型
PrivilegedAction<String> a = () -> "done";  //同样的 () -> "done" 却是 PrivilegedAction<String> 类型

答曰,根据 Lambda 表达式所处的上下文去感知。上下文决定了 Lambda 所期盼的类型,比如说变量声明类型,方法要求的输入参数类型,所以它必须是明确,不能模棱两可。所以一个 Lambda 表达式能否放在某处需要满足以下几个条件:

  1. 期盼的类型必须是一个功能性接口,这样就能唯一定位到那个抽象方法上去,确定方法签名,接下来就是
  2. Lambda 表达式的参数和类型要与那个 SAM 相匹配
  3. Lambda 表达式的返回类型也要与那个 SAM 相匹配
  4. Lambda 表达式抛出的异常也是那个 SAM 所允许的

说是四个条件,其实归根结底就是满足第一个条件。

由于能应用于 Lambda 表达式的功能性接口只有一个抽象方法,不存在重载的情况,所以它的参数类型和个数就是确定的了,所以我们的 Lambda 表达式可以进一步省略参数类型说明而简化为:

Comparator<String> c = (s1, s2) -> s1.compareToIgnoreCase(s2);

再往前一步,如果参那个 SAM 只有一个参数,连括号参数两边的括号也可以省去,变为:

FileFilter java = f -> f.getName().endsWith(".java");
button.addActionListener(e -> foo());

这个挺赞的,我喜欢这样的条种约定,像在 Scala 里诸多约定一样,使语法更加简洁明快。

什么可以作为 Lambda 表达式类型推断的上下文呢?State of the Lambda 表出 8 个所在:

  1. 变量声明
  2. 赋值
  3. 返回语句
  4. 数组初始化
  5. 方法或构造器的参数
  6. Lambda 表达式的内容体
  7. 三元操作的条件语句 (?:)
  8. 转型表达式处

我不喜欢官方对 Lambda 上下文的表述,原本 Lambda 类型推断和某些约定就是希望尽可能代码短小精悍;而此处对 Lambda 上文的列举更会令人迷惑不堪,应该就一句话:Lambda 表达式所处被期待什么类型就是它的上下文

如果说一种复杂的上下文就是作为重载方法的参数时,其实这也没什么特别之处,就是采用现有的规则,编译器会以一种最为接近的方式去 "猜测" Lambda 表达式实际表达的类型,尽可能的去消除二义性。同样的,你可在 IDE 中编译出错时,不断修正中进行体会,不须多说。

见识一下 JDK 以及 Scala 中的类型推断:

聪明的类型系统就要涉及到类型的推断,JDK5,6 中泛型的写法有点画蛇添足的意味,声明并初始化一个泛型实例要这么写:

List<Map<String, Integer>> properties  = new ArrayList<Map<String, Integer>>();

JDK7 聪明了一些,它认为后面那上 String 是多余的,所以在 JDK7 中相应的这么写:

List<Map<String, Integer>> properties = new ArrayList<>();

这已经很不错了,在泛型多层嵌套的时候更能体现出来,但是 Scala 觉得这样还不够好,它的体系里需要编译器更加智能,所以只要用个 var 或是 val 来声明变量,编译器就能推断出实际的类型,它是这样的:

var properties = List[Map[String, Int]]();

本文链接 https://yanbin.blog/jdk8-lambda-4-type-inference/, 来自 隔叶黄莺 Yanbin Blog

[版权声明] Creative Commons License 本文采用 署名-非商业性使用-相同方式共享 4.0 国际 (CC BY-NC-SA 4.0) 进行许可。

Subscribe
Notify of
guest

3 Comments
Inline Feedbacks
View all comments
楪夕
楪夕
10 years ago

你好,小弟是新手,有个问题想请教下。刚刚看了8.0的Comparator源码,发现跟之前的版本大不相同,是因为@FunctionalInterface的原因,让接口也可以定义方法体吗?

楪夕
楪夕
10 years ago
Reply to  Yanbin

jdk8.0变化很大啊,接口可以直接定义default修饰的方法,和静态方法