本篇主要介绍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. Cesium原理篇:3D Tiles(1)渲染调度

    Cesium在2016年3月份左右推出3D Tiles数据规范,在glTF基础上提供了LOD能力,定位就是Web环境下海量三维模型数据.虽然目前3D Tiles还是Beta阶段,有不少硬伤,但3D T ...

  2. Java代码块详解

    Java中代码块指的是用 {} 包围的代码集合,分为4种:普通代码块,静态代码块,同步代码块,构造代码块 普通代码块: 定义:在方法.循环.判断等语句中出现的代码块 修饰:只能用标签修饰 位置:普通代 ...

  3. 关于 myBatis 中的 jdbcType的细节问题

    前几天上线了一个 版本,第二天到公司的时候,在cat上发现了一个长sql查询非常耗时,几乎把线上的项目搞崩溃了.我马上开始排查问题.最终发现 球队的ID传过来的时候是String 类型的,但是在执行s ...

  4. 【Zookeeper】源码分析之服务器(二)

    一.前言 前面阐述了服务器的总体框架,下面来分析服务器的所有父类ZooKeeperServer. 二.ZooKeeperServer源码分析 2.1 类的继承关系 public class ZooKe ...

  5. java基础:学员状态查询

    package kaoshi; /* * 学员状态查询 * @作者:向往的生活 * @时间:2017.3.7 */ import java.util.Scanner; public class Stu ...

  6. selenium7种元素识别

    我们以百度主页搜索框为例:= <input autocomplete="off" maxlength="255" value="" c ...

  7. javascript写贪吃蛇游戏(20行代码!)

    <!doctype html> <html> <body> <canvas id="can" width="400" ...

  8. 通过Servlet生成验证码图片

    原文出自:http://www.cnblogs.com/xdp-gacl/p/3798190.html 一.BufferedImage类介绍 生成验证码图片主要用到了一个BufferedImage类, ...

  9. js数据类型:引用和5种基本数据类型

    javascript有2种基本数据类型:引用和基本数据类型 基本数据类型又有5个分类:布尔型.字符串.数值.undefined.NULL.

  10. Java日期获取需求大全

    刚进公司,作为熟悉技术,为公司做了一个小的点餐系统的网站,其中大量用到了时间日期作为唯一标示或是显示设置.特总结了一下和大家分享. package com.lucis.ordering.Utils; ...