本篇主要介绍Lambda的类型检查机制以及周边的一些知识。

类型检查

在前面的实践中,我们发现表达式的类型能够被上下文所推断。即使同一个表达式,在不同的语境下也能够被推断成不同类型。

这几天在码一个安卓应用,这里就举一个常见的的例子:

map.setOnMapLongClickListener(r -> {
    // do sth
});

map.setOnMapClickListener(r -> {
    // do sth
});

map.setOnMapDoubleClickListener(r -> {
    // do sth
});

显而易见,这里传入的三个Lambda表达式完全一样,但是却分别被推断成了长按监听器、单击监听器和双击监听器,因为表达式所处的方法告诉了编译器它需要的类型。

这种类型一定程度上受到上下文影响的表达式称为聚合表达式(Poly Expressions),这个概念在国内还是相对少见的,度娘了一下几乎没看到什么相关的解释,这里就引用下JSR-335的定义:

For some expressions, termed poly expressions, the deduced type can be influenced by the target type. The same expression can have different types in different contexts.

即推断类型受到目标类型的影响的表达式,称为聚合表达式。JSR-335的另一节中还明确表示,Lambda表达式、方法引用以及构造器引用都是聚合表达式,现在我们应该对这三种表达式的推断细节更加清楚了。

当然JSR-335中还给出了上下文影响推断类型的另一种形式:

After the type of the expression has been deduced, an implicit conversion from the type of the expression to the target type can sometimes be performed.

即推断出类型之后根据目标类型的需要进行隐式转换。

若要深入类型检查的机制,我们还需要了解何为SAM的函数类型。

函数类型

书中2.4节中给出了一个通俗的解释:函数式接口的函数类型就是其唯一一个抽象方法的类型,即其类型参数(泛型类型) + 参数类型 + 返回类型 + 抛出(异常)类型。

但是有一个值得注意的地方,任何类和接口都会隐式声明Object类中的抽象方法,诸如equals()toString()等,因此如果在接口中显式声明了这些方法,也不会干扰到真正的目标方法的判定,换句话说,这些Object类中的抽象方法都不会算数,定义SAM时依旧需要满足函数式接口的定义,即有且仅有一个抽象方法,如:

public interface Foolable {
    String toString();
    String shoutOutLoud();
}

调用doSomethingOnFoolsDay(() -> "Let's fool others!");时仍然会正常输出:

private static void doSomethingOnFoolsDay(Foolable foolable) {
    System.out.println(foolable.shoutOutLoud());
}

以上因素会使情况变得稍稍复杂。

Comparator<T>在此时就很有研究价值,除去目标函数int compare(T o1, T o2)default方法以及static方法,它还显式声明了boolean equals(Object obj)

另外一个因素是有关泛型的类型擦除,这里方便起见我就抄上书中的例子:

interface Foo1 {
    void bar(List<String> args);
}

interface Foo2 {
    void bar(List args);
}

此时如果有一个接口同时继承自这两个父接口,那么其目标函数的类型应该是void bar(List args)所对应的类型。

书中给的解释(可能是翻译的问题)有点读不通,这里同样给出JSR-335中扒出来的解释:

If a lambda expression is compatible with its target type, T, then the type of the expression is T.

A lambda expression is compatible in an assignment, invocation, or casting context with type T if T is a functional interface type and the expression is congruent with a function descriptor derived from T.

匹配函数类型

匹配的规则很简单,只需要保证最低限度的兼容性即可,涉及到开头提到的四个方面:

  • 参数数量
  • 参数类型
  • 返回类型

    如果返回类型为void的话,就应该是一个语句或者没有返回值的方法调用,当然表达式或者有返回值方法调用也行,不过它们的结果会被丢弃。

    返回类型不为void的话,Lambda就应该返回一个与之兼容的值。
  • 抛出(异常)类型

    作者在书中坦言,Java8 Lambda中,异常处理是一个问题。

    Lambda可以抛出检查异常(Checked Exception),前提是该SAM接口的函数类型声明抛出了该类或其父类的异常

    我们用URL来试验异常抛出的问题,首先定义:

    public interface AcuratelyFoolable {
        void doFool();
    }

    其次:

    private static void doSomethingOnFoolsDay(AcuratelyFoolable acuratelyFoolable) {
        acuratelyFoolable.doFool();
    }

    再分别初始化URL类、使用实例引用,注意区分一般情况下捕捉检查异常以及Lambda处理检查异常的方式。

    URL url = null;
    try {
        url = new URL("http://fools.fool");
    } catch (MalformedURLException e) {
        e.printStackTrace();
    }
    
    /*
     * attention here
     */
    doSomethingOnFoolsDay(url::openConnection);

    我们成功tryURL类初始化时会抛出的MalformedURLException,但是这样依旧无法通过编译,我们都知道URL类的openConnection(...)会抛出IO异常:

    public URLConnection openConnection() throws IOException {
        return this.handler.openConnection(this);
    }

    所以我们应该修改函数类型让它声明抛出此类(或其父类)异常:

    public interface AcuratelyFoolable {
        void doFool() throws IOException;
    }

    然后在方法中try这个抛出点:

    private static void doSomethingOnFoolsDay(AcuratelyFoolable acuratelyFoolable) {
        try {
            acuratelyFoolable.doFool();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    注意比较这两处与上文相比所修改的地方。

小结

本篇所用的代码:

FoolsDay.java

import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;

/**
 * Created by hwding on 4/2/17.
 */
public class FoolsDay {
    public static void main(String[] args) {
        doSomethingOnFoolsDay(() -> "Let's fool others!");
        URL url = null;
        try {
            url = new URL("http://fools.fool");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        /*
         * attention here
         */
        doSomethingOnFoolsDay((AcuratelyFoolable) url::openConnection);
    }

    private static void doSomethingOnFoolsDay(Foolable foolable) {
        System.out.println(foolable.shoutOutLoud());
    }
    private static void doSomethingOnFoolsDay(AcuratelyFoolable acuratelyFoolable) {
        try {
            acuratelyFoolable.doFool();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Foolable.java

/**
 * Created by hwding on 4/2/17.
 */
public interface Foolable {
    String toString();
    String shoutOutLoud();
}

AcuratelyFoolable.java

import java.io.IOException;

/**
 * Created by hwding on 4/15/17.
 */
public interface AcuratelyFoolable {
    void doFool() throws IOException;
}

运行结果:

Let's fool others!

Lambda&Java多核编程-7-类型检查的更多相关文章

  1. Lambda&Java多核编程-5-函数式接口与function包

    从前面的总结中我们知道Lambda的使用场景是实现一个函数式接口,那么本篇就将阐述一下何为函数式接口以及Java的function包中提供的几种函数原型. 函数式接口 早期也叫作SAM(Single ...

  2. Lambda&Java多核编程-6-方法与构造器引用

    在Lambda&Java多核编程-2-并行与组合行为一文中,我们对Stream<Contact>里的每一位联系人调用call()方法,并根据能否打通的返回结果过滤掉已经失效的项. ...

  3. 初探Lambda表达式/Java多核编程【0】从外部迭代到内部迭代

    开篇 放假前从学校图书馆中借来一本书,Oracle官方的<精通Lambda表达式:Java多核编程>. 假期已过大半才想起来还没翻上几页,在此先推荐给大家. 此书内容及其简洁干练,如果你对 ...

  4. 初探Lambda表达式/Java多核编程【1】从集合到流

    从集合到流 接上一小节初探Lambda表达式/Java多核编程[0]从外部迭代到内部迭代,本小节将着手使用"流"这一概念进行"迭代"操作. 首先何为" ...

  5. 初探Lambda表达式/Java多核编程【2】并行与组合行为

    今天又翻了一下书的目录,第一章在这之后就结束了.也就是说,这本书所涉及到的新的知识已经全部点到了. 书的其余部分就是对这几个概念做一些基础知识的补充以及更深层次的实践. 最后两个小节的内容较少,所以合 ...

  6. 初探Lambda表达式/Java多核编程【3】Lambda语法与作用域

    接上一篇:初探Lambda表达式/Java多核编程[2]并行与组合行为 本节是第二章开篇,前一章已经浅显地将所有新概念点到,书中剩下的部分将对这些概念做一个基础知识的补充与深入探讨实践. 本章将介绍L ...

  7. 初探Lambda表达式/Java多核编程【4】Lambda变量捕获

    这周开学,上了两天感觉课好多,学校现在还停水,宿舍网络也还没通,简直爆炸,感觉能静下心看书的时间越来越少了...寒假还有些看过书之后的存货,现在写一点发出来.加上假期两个月左右都过去了书才看了1/7都 ...

  8. 【java编程-Javassist】秒懂Java动态编程(Javassist研究)

    作者:ShuSheng007 来源:CSDN 原文:https://blog.csdn.net/ShuSheng0007/article/details/81269295 版权声明:本文为博主原创文章 ...

  9. Java函数式编程和lambda表达式

    为什么要使用函数式编程 函数式编程更多时候是一种编程的思维方式,是种方法论.函数式与命令式编程的区别主要在于:函数式编程是告诉代码你要做什么,而命令式编程则是告诉代码要怎么做.说白了,函数式编程是基于 ...

随机推荐

  1. Spark性能优化之道——解决Spark数据倾斜(Data Skew)的N种姿势

    原创文章,同步首发自作者个人博客转载请务必在文章开头处注明出处. 摘要 本文结合实例详细阐明了Spark数据倾斜的几种场景以及对应的解决方案,包括避免数据源倾斜,调整并行度,使用自定义Partitio ...

  2. iOS回顾笔记(04) -- UIScrollView的基本使用详解

    html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,bi ...

  3. NodeJS链接MongDB

    创建一个mongdb.js var mongodb = require('mongodb') // 创建数据库服务的链接 var server = new mongodb.Server( 'local ...

  4. shell [ff: 未找到命令

    在学习shell脚本时遇到一个问题:  [ff: 未找到命令 相信很多初学者都会遇到,再次说明一下,希望对大家有所帮助: shell脚本代码如下: #!/bin/bash echo -n " ...

  5. 解决Gerrit的git unpack error问题和error Missing unknown ec867cebfd2be97c3603c45fac03c75dcf68d0ca

    参考链接:http://www.cnblogs.com/yuxc/p/3508964.html 解决方法: 由于帖子里面用的是mysql数据库,而我用的是h2数据库,还特意自己去找了H2数据库的进入方 ...

  6. 关于 jquery html 动态添加的元素绑定事件——On()

    Ajax动态生成的数据,动作绑定需要重新执行 $(document).on('click','.btn1',function(){}); 替换: $('btn1').on('click') = fun ...

  7. C风格字符串和C++ string 对象赋值操作的性能比较

    <<C++ Primer>> 第四版 Exercise Section 4.3.1 部分Exercise 4.2.9 习题如下: 在自己本机执行如下程序,记录程序执行时间: # ...

  8. 从 Spring 2.5 开始就可以使用注解来配置依赖注入,而不是采用 XML 来描述一个 bean。

    1.在 XML 注入之前进行注解注入,因此后者可以被前者重写. 2.在默认情况下注解在 Spring 容器中不打开,需要配置启动. <beans xmlns="http://www.s ...

  9. dfs 无向图两节点间的所有路径

    标题:风险度量 X星系的的防卫体系包含 n 个空间站.这 n 个空间站间有 m 条通信链路,构成通信网.两个空间站间可能直接通信,也可能通过其它空间站中转. 对于两个站点x和y (x != y), 如 ...

  10. Asp.NetCore1.1版本没了project.json,这样来生成跨平台包

    本章将要和大家分享的是Asp.NetCore1.1版本去掉了project.json后如何打包生成跨平台包, 为了更好跟进AspNetCore的发展,把之前用来做netcore开发的vs2015卸载后 ...