本篇主要介绍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. Java设计模式之《组合模式》及应用场景

    摘要: 原创作品,可以转载,但是请标注出处地址http://www.cnblogs.com/V1haoge/p/6489827.html 组合模式,就是在一个对象中包含其他对象,这些被包含的对象可能是 ...

  2. SqlService性能检测和优化工具

    工具概要 如果你的数据库应用系统中,存在有大量表,视图,索引,触发器,函数,存储过程,sql语句等等,又性能低下,而苦逼的你又要对其优化,那么你该怎么办?哥教你,首先你要知道问题出在哪里?如果想知道问 ...

  3. 一个好用的几乎没有Callback的Android异步库

    android-async-task 这是一个Android平台处理复杂异步任务的库 (https://github.com/gplibs/android-async-task) 1. 安装方法 gr ...

  4. Java反射机制深度剖析

    版权声明:本文为博主原创文章,转载请注明出处,欢迎交流学习! Java反射机制是Java语言中一种很重要的机制,可能在工作中用到的机会不多,但是在很多框架中都有用到这种机制.我们知道Java是一门静态 ...

  5. Golang版protobuf编译

    官方网址: https://developers.google.com/protocol-buffers/ (需要FQ) 代码仓库: https://github.com/google/protobu ...

  6. gcc下c++的对象模型 (1)

    所有示例代码在如下环境中执行 ubuntu 16.04.4 (64位) gcc version 5.4.0 开启std11 gdb version 7.11.1 1. 空类的大小 定义一个空类A,实例 ...

  7. Exchange无法发送邮件 未找到匹配的连接器来路由外部收件人解决办法

    使用命令行管理程序创建发送连接器 本示例创建发送连接器,用于集线器传输服务器 HubA 向 Internet 发送电子邮件.   复制 New-SendConnector -Name "In ...

  8. ipconfig显示IP地址情况

    1.以太网适配器 Local Area Connection 若电脑是本地连接,则ipv4地址是本机的ip地址,默认网关一般为本机所连接路由器的地址. 2.无线局域网适配器 Wireless Netw ...

  9. 查看 NDK 版本

    打开Android Studio , 打开左上角的菜单, File => Settings...  打开一个弹窗. 然后在 Appearance & Behavior =>Syst ...

  10. iOS开发之Segue

    Storyboard上每一根用来界面跳转的线,都是一个UIStoryboardSegue对象(简称Segue). 每一个Segue对象,都有3个属性: (1)唯一标识 @property (nonat ...