本篇主要介绍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);
我们成功
try
了URL
类初始化时会抛出的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!