对于只包含一个抽象方法的接口,可以通过lambda表达式来创建该接口的对象。这种接口被称为函数式接口。
例如:以Arrays.sort方法为例。该方法的第二个参数需要一个Comparator接口的实例。
Arrays.sort(words, (first, second) ->该表达式背后,Arrays.sort方法会接收一个实现了Comparator<String>接口的类的实例。调用该对象的compare方法会执行lambda表达式中的代码。这些对象和类的管理完全依赖于如何实现,因此比传统的内部类效率更高。最好将一个lambda表达式想象成一个函数,而不是一个对象,并记住它可以被转换为一个函数式接口。
Integer.compare(first.length(), second.length())
);
事实上,函数式接口的转换是在java中使用lambda表达式能做的唯一一件事。在其他支持函数文本的编程语言中,你可以声明像(String, String) -> int 这样的函数类型,声明这种类型的变量,并使用这些变量来保存函数表达式。但是在Java中是使用接口概念,没有能够使用函数类型。
不能将一个lambda表达式赋值给一个Object类型的变量,因为Object不是一个函数式接口。
任何一个lambda表达式都可以等价转换成现在所使用的API中对应的函数式接口。
可以在任意函数式接口上标注@FunctionalInterface注解,这样的好处是:编译器会检查该注解的实体,检查它是否是只包含一个抽象方法的接口;在javadoc页面也会包含一条声明,说明这个接口是一个函数式接口。
当一个lambda表达式被转换为一个函数式接口的实例时,请注意处理检查期异常。如果lambda表达式中可能会抛出一个检查期异常,那么该异常需要在目标接口的抽象方法中进行声明。例如,一下表达式会产生一个错误:
Runnable sleeper = () -> {错误是因为Thread.sleep(1000)可以抛出一个检查期InterruptedException
System.out.println("XXX");
Thread.sleep(1000);
};
因为Runnable.run 不能抛出任何异常,所以这个赋值不合法。有2个方法解决:
1. 在lambda表达式中捕获异常。如下
Runnable sleeper = () -> {
System.out.println("XXX");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
2. 将lambda表达式赋给一个其抽象方法可以抛出异常的接口。例如,Callable接口的call方法可以抛出任何异常,可以将lambda表达式赋给Callable<Void>(要添加return null; 因为call抽象方法是有返回值的)。如下
Callable sleeper = () -> {
System.out.println("XXX");
Thread.sleep(1000);
return null;
};