List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> list2 = list.stream()
.filter(e -> e.length() > 3)
.peek(e -> System.out.println("first: " + e))
.filter(e -> e.length() > 4)
.peek(e -> System.out.println("second: " + e))
.collect(Collectors.toList()).forEach(System.out::println);
上面这一串代码就是利用lambda表达式,以及Stream流(不同于IO流)完成对集合的过滤(filter)、遍历输出(forEach)等操作。
第一次看到lambda表达式可能会比较萌比,下面咱们慢慢聊。
上面那串代码中,出现的filter(...)、peek(...)、forEach(...)
似乎都没见过呢,这样还能接受,可是e -> e.length() > 3
是个什么鬼?System.out::println
这东西也没见过哇?其实这些就是所谓的lambda表达式,也就是大家常说的函数式编程。
接下来咱们聊聊Java8中的新特性,函数接口。
通俗的话讲:函数接口是指只含有一个抽象方法的接口,在Java8中,接口里面可以存在已实现的方法,如:default
默认方法、static
静态方法。
函数接口长这样:
@FunctionalInterface
public interface Supplier<T> {
T get();
}
注意:@FunctionalInterface
注解的作用,当函数接口中存在两个或两个以上的抽象方法时,编译会报错。
前言中谈到的e -> e.length() > 3
其实就是函数接口类型的实例。它是一个实例,那么问题来了,它的类型是什么?答案是:它的类型是由上下文推导而来。
下面看一个简单的例子:
List<String> list = Arrays.asList("one", "two", "three", "four");
list.forEach(e -> System.out.println(e));
通过查看源码,发现forEach
方法接收的参数类型为:Consumer super T> action
,因此可推断lambda表达式的类型:
Consumer<String> action = e -> System.out.println(e);
如何理解这句代码呢?先给出java.util.function.Consumer
的源码:
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
...(省略default方法)
}
可以这样理解:e -> System.out.println(e)
,就是实现了Consumer
接口中的accept
方法的实例。->
前面部分e
表示 accept
方法的入参,->
后面部分表示 accept
方法的实现部分。
注意:
{}
括起来。return
。如果方法体有多行代码,则不能省略return
。OK!带有->
的lambda表达式,相信大家都基本理解了吧,那么这个System.out::println
又是个什么鬼呢?这个是方法引用,也是lambda表达式的一种形式。
之前说过,lambda表达式就是函数接口类型的实例,而创建该实例的过程,需要实现函数接口中的抽象方法。因此,可以采用引用的方式,即引用已定义好的方法去实现函数接口中的方法。
OK!那么System.out::println
该如何理解?同样根据上下文推断lambda表达式的类型:
Consumer action = System.out::println;
System.out::println
的函数接口类型为java.util.function.Consumer
,即在产生该实例对象时,引用了System.out
对象中的println
方法,去实现Consumer
接口中的accept
抽象方法。简而言之,即:引用println
方法去实现接口中的抽象方法。
方法引用有很多种,下面主要讲述以下几种:
Class::new
ClassName::methodName
instanceReference::methodName
ClassName::methodName
格式:类名::new
public interface OneFunction {
Man create(String name);
}
public class Man {
private String name;
public Man(){
this.name = "小婴儿";
}
public Man(String name){
this.name = name;
}
public String say(){
return "my name is: "+ name;
}
}
public class TestMan {
public static void main(String[] args){
//利用lambda表达式,创建函数接口的实例对象
//函数接口中的create抽象方法引用Man中的构造器来实现
OneFunction manLambda= Man::new;
//函数接口实例对象,调用函数,产生man对象。
Man man = manLambda.create("小屁孩");
//man对象调用方法
System.out.println(man.say());
}
}
关于构造器引用需要注意的是:函数接口中的抽象方法,具体引用哪一个构造方法去实现,是根据抽象方法的形参来判断的。 OneFunction
接口中的create
方法是有参数的,因此引用有参构造去实现。
格式:类名::静态方法名
public interface TwoFunction {
String action(String name);
}
public class Student {
public static String study(String subject){
return "I am studying : "+ name;
}
}
public class TestMan {
public static void main(String[] args){
TwoFunction lambda = Student::study;
System.out.println(lambda.action("English"));
}
}
引用静态方法的条件是:函数接口中抽象方法的形参、返回值类型与静态方法中的形参、返回值类型一致。
格式:实例名::成员方法名
public class Teacher {
public Teacher(){}
public String teach(Integer days){
return "the teach has " + days + " day's course";
}
}
public interface ThreeFunction{
void apply(Integer num);
}
public class Test {
public static void main(String[] args){
Teacher teacher = new Teacher();
ThreeFunction lambda = teacher::teach;
System.out.println(lambda.teach(5));
}
}
实例对象的成员方法引用的条件:和静态方法引用的条件一样,函数接口中抽象方法的形参、返回值类型与静态方法中的形参、返回值类型一致。
格式:类名::成员方法名
由于cherry只是一位资深的平庸开发者,在学习该用法的时候遇到了许多问题。下面贴出问题给大家看看,还望各位大牛们别取笑我,毕竟我只是个小菜啦。
public class Teacher {
public Teacher(){}
public int teach(){
return 0;
}
public static int teach1(String days){
return 0;
}
}
public interface ThreeFunction{
int action();
}
public class Test {
public static void main(String[] args){
ThreeFunction lambda = Teacher::teach;//这个代码会报错
}
}
注意上面测试代码,ThreeFunction lambda = Teacher::teach
编译不能通过。报错信息如下:
提示为:非静态方法不能被引用在静态上下文中。
该如何解决呢?
下面引用一篇博客中的解释:
String::toString 等价于lambda表达式 (s) -> s.toString() ;
这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象。
解决办法:
在ThreeFunction
函数接口的抽象方法action
中添加一个形式参数,且类型为Teacher
。
在此本文对类型上的实例方法引用需要满足的条件进行如下猜想:
其实还有人提到了,函数接口中抽象方法的形参个数为2个,成员方法的形参省略第一个保留后一个时,也能成功引用。
(未完待续)请看下一片博客
深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)
lambda方法引用总结——烧脑吃透
Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)
java8新特性 lambda Stream map(函数式编程)
Java 8 Lambda 表达式
Java 8的新特性—终极版