在哪里可以使用Lambda表达式。在上一个例子中,把Lambda
赋给了一个Comparator
类型的变量。也可以在之前实现的filter
方法中使用Lambda
:
List<Apple> greenApples = filter(inventory, (Apple a) -> "green".equals(a.getcolor()));
到底在哪里可以使用Lambda
呢?可以在函数式接口上使用Lambda
表达式。在上面的代码中,可以把Lambda
表达式作为第二个参数传给filter
方法,因为它这里需要Predicate
,而这是一个函数式接口。
在上一个例子中,为了参数化filter
方法的行为而创建的Predicate
接口,它就是一个函数式接口。因为Predicate
仅仅定义了一个抽象方法:
public interface Predicate<T> {
boolean test (T t);
}
一言以蔽之,函数式接口就是只定义一个抽象方法的接口。已经知道了Java API
中的一些其他函数式接口,如Comparator
和Runnable
。
//java.uti1.Comparator
public interface Comparator<T> {
int compare(T ol,T o2);
}
//java.lang.Runnable
public interface Runnable {
void run();
}
//java.awt.event.ActionListener
public interface ActionListener extends EventListener {
void actionPerformed(ActionEvent e);
}
//java.util.concurrent.Callable
public interface Callable<V> {
V call();
}
//java.security.PrivilegedAction
public interface PrivilegedAction<V> {
V run();
}
注意,接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
测验:函数式接口
下面哪些接口是函数式接口?
public interface Adder {
int add(int a, int b);
}
public interface SmartAdder extends Adder {
int add(double a, double b);
}
public interface Nothing {
}
答案:只有Adder是函数式接口。
SmartAdder不是函数式接口,因为它定义了两个叫作add的抽象方法(其中一个是从Adder那里继承来的)。
Nothing也不是函数式接口,因为它没有声明抽象方法。
用函数式接口可以干什么呢?Lambda
表达式允许直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。用匿名内部类也可以完成同样的事情,只不过比较笨拙:需要提供一个实现,然后再直接内联将它实例化。下面的代码是有效的,因为Runnable
是一个只定义了一个抽象方法run
的函数式接口:
//使用Lambda
Runnable r1 = () -> System.out.println("Hello World 1");
//使用匿名类
Runnable r2 = new Runnable() {
public void run() {
System.out.println("Hello World 2");
}
};
public static void process(Runnable r) {
r.run();
}
//打印“Hello World 1"
process(r1);
//打印“Hello World 2”
process(r2);
//利用直接传递的Lambda打印“Hello World 3”
process(() -> System.out.println("Hello World 3"));
函数式接口的抽象方法的签名基本上就是Lambda
表达式的签名。将这种抽象方法叫作函数描述符。例如,Runnable
接口可以看作一个什么也不接受什么也不返回(void)
的函数的签名,因为它只有一个叫作run
的抽象方法,这个方法什么也不接受,什么也不返回(void)
。
在前面使用了一个特殊表示法来描述Lambda
和函数式接口的签名。() -> void
代表了参数列表为空,且返回void
的函数。这正是Runnable
接口所代表的。举另一个例子,(Apple, Apple )-> int
代表接受两个Apple
作为参数且返回in
t的函数。
]现在,只要知道Lambda
表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法就好了,当然这个Lambda
表达式的签名要和函数式接口的抽象方法一样。比如,在之前的例子里,可以像下面这样直接把一个Lambda
传给process
方法:
public void process(Runnable r) {
r.run();
}
process(() -> System.out.println("This is awesome!!"));
此代码执行时将打印"This is awesome!!"
。Lambda表达式() -> System.out.println("This is awesome!!")
不接受参数且返回void。 这恰恰是Runnable
接口中run
方法的签名。
“为什么只有在需要函数式接口的时候才可以传递Lambda
呢?”语言的设计者也考虑过其他办法,例如给Java
添加函数类型。但是他们选择了现在这种方式,因为这种方式自然且能避免语言变得更复杂。此外,大多数Java
程序员都已经熟悉了具有一个抽象方法的接口的理念(例如事件处理)。
测验:在哪里可以使用Lambda?
以下哪些是使用Lambda表达式的有效方式?
(1) execute(() -> {});
public void execute(Runnable r) {
r.run()
};
(2)public Callable<String> fetch() {
return () -> "Tricky example ;-)";
}
(3)PredicatecApple> p = (Apple a) -> a.getWeight();
答案:只有1和2是有效的。
第一个例子有效,是因为Lambda()->{}具有签名() -> void,这和Runnable中的抽象方法run的签名相匹配。请注意,此代码运行后什么都不会做,因为Lambda是空的!
第二个例子也是有效的。事实上,fetch方法的返回类型是Callable<String>。Callable<String>基本上就定义了一个方法,签名是() -> String,其中T被String代替了。因为Lambda() -> "Trickyexample;-)"的签名是() -> String,所以在这个上下文中可以使用Lambda。
第三个例子无效,因为Lambda表达式(Apple a) -> a.getWeight()的签名是(Apple)->
Integer,这和Predicate<Apple>:(Apple) -> boolean中定义的test方法的签名不同。
@FunctionalInterface又是怎么回事?
如果去看看新的JavaAPI,会发现函数式接口带有@FunctionalInterface的标注。这个标注用于表示该接口会设计成一个函数式接口。如果用@FunctionalInterface定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。例如,错误消息可能是“Multiple non-overridingabstract methods found in interface Foo",表明存在多个抽象方法。请注意,@FunctionalInterface不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。它就像是@Override标注表示方法被重写了。