Lambda&Java多核编程-7-类型检查

时间:2023-01-24 11:37:57

本篇主要介绍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!