在前两章学习了 Lambda 表达式的写法和Stream的基本操作方法,可以囫囵吞枣简单的写写代码了。那么不理解的地方就是,为什么可以将Lambda 表达式作为参数传递?为什么Lambda 表达式固定的括号里不带参数,或者带参数?下面一步一步地进行讲解。
在 Java 里,所有方法参数都有固定的类型,如下代码
public int add(int x, int y) {
return x + y;
}
// 调用方法
add(2, 3);
代码中参数x和y都是int类型,函数调用时传入两个int数值就可以了,那么函数调用传入Lambda 表达式,如下代码所示
new Thread(() -> System.out.println("函数式编程")).start();
代码里的Lambda 表达式是什么类型呢?看一下java源代码中Thread的定义
// Thread定义
public Thread(Runnable target) {
this(null, target, "Thread-" + nextThreadNum(), 0);
}
// Runnable定义
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
代码中Runnable是接口,该接口标志了@FunctionalInterface注解,可以看出@FunctionalInterface注解的接口和Lambda 表达式紧密联系的,Runnable接口与Lambda 表达式写法转换如下所示
// Thread传入Runnable接口的匿名内部类
new Thread(new Runnable() {
public void run() {
System.out.println("函数式编程");
}
}).start();
// 转换成Lambda表达式步骤
// 1、Thread构造方法只能传入Runnable接口,所以匿名实现类new Runnable(){}省略
// 2、Runnable接口只有一个接口run方法,所以public void run省略
// 3、run方法没有参数,所以只留下(),加上->分割符区分参数和代码块
// 转成变成Lambda表达式如下
new Thread(() -> {System.out.println("函数式编程");}).start();
// 代码块里面只有一句代码,所以省略代码块{}部分,最终变成
new Thread(() -> System.out.println("函数式编程")).start();
再看一下带参数的例子,比如java源代码Stream中的filter方法定义,代码如下
// filter方法传入Predicate接口
Stream<T> filter(Predicate<? super T> predicate);
// Predicate接口定义
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
}
Predicate接口同样标志了@FunctionalInterface注解,Predicate接口与Lambda 表达式写法转换如下所示
// filter方法传入Predicate接口的匿名内部类
Stream.of("nodejs", "java", "python", "javascript")
.filter(new Predicate<String>() {
public boolean test(String s) {
return s.startsWith("java");
}
});
// 转换成Lambda表达式步骤
// 1、filter方法只能传入Predicate接口,所以匿名实现类new Predicate<String>(){}省略
// 2、Predicate接口只有一个接口test方法,所以public boolean test省略
// 3、test方法有参数,所以保留(String s),加上->分割符区分参数和代码块
// 转成变成Lambda表达式如下
Stream.of("nodejs", "java", "python", "javascript")
.filter((String s) -> {
return s.startsWith("java");
}
);
// 代码块里面只有一句代码,且返回值也是同样的代码,可以省略代码块{}部分
Stream.of("nodejs", "java", "python", "javascript")
.filter((String s) -> s.startsWith("java"));
// 定义参数类型String可省略,并且只有一个参数,()也可省略,但如果是多个参数就必须加上(),逗号分隔参数,最终变成如下代码
Stream.of("nodejs", "java", "python", "javascript")
.filter(s -> s.startsWith("java"));
如果代码使用了已经定义好的方法,且此方法引用仅仅涉及单一方法,Lambda表达式提供更快捷的写法,如下代码
Stream.of("nodejs", "java", "python", "javascript")
.filter(s -> s.isBlank());
// String::isBlank方法引用就是Lambda表达式s -> s.isBlank()的快捷写法
Stream.of("nodejs", "java", "python", "javascript")
.filter(String::isBlank);
综合上面Thread和Stream这两个例子中,匿名内部类一步一步转换成Lambda 表达式看出,@FunctionalInterface函数接口就是Lambda 表达式的类型,并且@FunctionalInterface注解的接口只有一个可以实现的抽象方法。
下面列举的是6个基本的函数接口,每个函数接口都有固定的用法
函数接口 | 抽象方法 | 接口使用描述 | 方法引用示例代码 |
UnaryOperator<T> | T apply(T t) | 对象处理后返回同类型对象 | String::toLowerCase |
BinaryOperator<T> | T apply(T t1, T t2) | 两个同类型对象处理后变成一个对象 | Integer::sum |
Function<T, R> | R apply(T t) | 对象处理后变成另外一个对象 | Integer::toBinaryString |
Predicate<T> | boolean test(T t) | 对象判断 | String::isEmpty |
Supplier<T> | T get() | 生成一个对象 | LocalDate::now |
Consumer<T> | void accept(T t) | 消费对象 | System.out::println |