一、缺省方法
首先看一段用Java 8写的代码:
//create a list, and add 3 elements
List l = new ArrayList() {
{
add("Hello");
add("World");
add("Peace");
}
};
//print element in l one by one
l.forEach(System.out::println);
首先创建一个 List 对象,并添加了三个元素到其中,然后调用 List 的 forEach 方法将其中的元素一个一个打印出来。
但是我们都知道 List 是一个接口,其中没有 forEach 方法,而如果直接在 List 接口中添加新的方法定义,则会破坏向下兼容性,因为以前的程序在新的Java环境里因缺少对该方法的实现而报错。Java 8中解决这一问题的手段就是使用缺省方法。
缺省方法是在接口中定义方法实现的一种方式,并且保证所有已经存在的子类的兼容性,所以实现了接口的类默认都拥有在接口中定义的缺省方法,这有点像一个抽象类。当一个子类中没有覆盖缺省方法时,则对子类的该方法的调用将调用接口中的实现。缺省方法的声明方式如下:
public interface DefaultInterface {
default int getValue(){
return 1;
}
}
只需要在方法前加关键字 default 即可。
缺省方法提供了一种在不破坏现在接口实现的情况下,给接口添加新的功能的途径。查看集合类的源码就会发现Java 8的集合类接口中添加了很多缺省的方法实现,用于提供对集合的共同的操作,如上所示的 forEach 方法。
一个类可以实现多个接口,如果其中有多于一个接口有相同的缺省方法时,则子类需要定义自己的实现,否则会报编译错误:
interface A{
default String name(){
return "A";
}
}
interface B{
default String name(){
return "B";
}
}
class C implements A, B{
public String name(){
return A.super.name();
}
}
二、forEach() 方法
forEach() 方法的源码如下:
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
这里 this 对象就是 List 对象本身,t 就是 List 中的遍历的元素。写在我们熟悉的方式就是:
for(String s: l){
action.accept(s);
}
三、java.util.function.Consumer 接口
看上面 forEach() 方法参数,需要的是一个 Consumer 对象,这里我们传入的是一个 System.out::println 。 Consumer 是新增的函数编程接口中的一个,看它的介绍:
Represents an operation that accepts a single input argument and returns no result.
而双冒号 :: 操作符也是新增的一个操作符,表示一个函数引用。结合起来理解应该就是:一个类的任何一个方法,如果它只有一个参数,并且没有返回值,则在需要Consumer参数的地方都可以把此方法作为函数引用传入。
可以作个实验。先定义以下类:
class TestConsumer{
public static TestConsumer INSTANCE = new TestConsumer();
public void sayHello(String s){
System.out.println("Hello "+s);
}
}
然后替换 System.out::println 为 TestConsumer.INSTANCE::sayHello,如下:
//create a list, and add 3 elements
List l = new ArrayList() {
{
add("Hello");
add("World");
add("Peace");
}
};
//print element in l one by one
l.forEach(TestConsumer.INSTANCE::sayHello);
看打印结果:
Hello Hello
Hello World
Hello Peace
证明我们的猜想是对的。
再试一下静态方法,修改上面的TestConsumer类,添加一个静态方法:
public static void greeting(String s){
System.out.println("Greeting "+s);
}
还是替换 forEach() 中的参数:
l.forEach(TestConsumer::greeting);
这次和普通的静态方法一样,直接通过类访问函数对象。打印结果:
Greeting Hello
Greeting World
Greeting Peace
由上证明,Consumer接口对于静态和实例方法都是适用的。
另外,我们也可以实现一个方法,其他要求传入一个Consumer对象:
public class TestConsumer {
public static void main(String[] args) {
TestConsumer test = new TestConsumer();
test.tryConsumer(System.out::println, "hello");
}
public void tryConsumer(Consumer c, String t){
c.accept(t);
}
}
仔细想来,这其实就有点类似于C/C++的传入方法指针之类的东西:Consumer可以根据传入的函数引用不用而执行不同的操作。