本篇主要介绍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. H3 BPM总部乔迁 开启新篇章 筑梦新征程

    近日,国际一流的BPM解决方案服务商H3 BPM总部迎来乔迁之喜,办公新址泰邦科技大厦位于国家火炬计划软件产业基地.国家高新技术产业标准化示范区.国家文化和科技融合示范基地的深圳高新技术产业园区,办公 ...

  2. 关于Edittext默认弹出软键盘为数字键

    如果说我们只是输入数字的话,我们可以直接在xml文件中: android:inputType="number" 如果是身份证类型的话,我们可以这样: android:inputTy ...

  3. Selenium 使用过程遇到问题随笔

    最近正在学习Selenium,自学是比较难的,也很感谢网络环境中,各位大大的博文帮助. 也希望在此能够记录一下从小白学习使用selenium测试的过程,也希望能对别人有所帮助. 关于环境部署,以及入门 ...

  4. MATLAB(5)——生成归一化直方图

    作者:桂. 时间:2017-03-10  22:13:36 链接:http://www.cnblogs.com/xingshansi/p/6533579.html 声明:欢迎转载,不过记得注明出处哦~ ...

  5. 微信企业号接收消息(使用SpringMVC)

    微信企业号接收消息(使用SpringMVC) 微信企业号接收消息(使用SpringMVC) 将应用设置在回调模式时,企业可以通过回调URL接收员工回复的消息,以及员工关注.点击菜单.上报地理位置等事件 ...

  6. 从客户端中检测到有潜在危险的 Request.Form或Requst.String的值。

    在ASP中客户端请求服务时会出现"从客户端中检测到有潜在危险的 Request.Form或Requst.QueryString的值.",原因是在web.config配置文件中存在这 ...

  7. Spring-Mybatis配置多数据源

    可以参考: http://www.cnblogs.com/ityouknow/p/6102399.html 需要一个DatabaseConfiguration类,实现 TransactionManag ...

  8. XShell连接本地Ubuntu虚拟机

    VMware Workstation 安装好本地虚拟机之后,直接在虚拟机上敲命令着实不方便. 这个时候我们就需要一个远程命令工具来管理虚拟机,这里推荐使用XShell远程命令行工具 1.下载工具 直接 ...

  9. 【转】深入理解RunLoop

    RunLoop 是 iOS 和 OS X 开发中非常基础的一个概念,这篇文章将从 CFRunLoop 的源码入手,介绍 RunLoop 的概念以及底层实现原理.之后会介绍一下在 iOS 中,苹果是如何 ...

  10. 防火墙之netfailt、iptables详解

    [TOC] Iptables入门 # 1.iptables介绍 linux的包过滤功能,即linux防火墙,它由netfilter 和 iptables 两个组件组成. netfilter 组件也称为 ...