个人认为该文章对lambda的解释很透彻:https://blog.csdn.net/GoGleTech/article/details/79454151
1.概念:
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次
2.lambda表达式的语法:
参数,箭头(->)以及一个表达式
例如:
(String first,String second) ->
{
if(first.length() < second.length()) return -1;
else if(first.length() > second.length()) return 1;
else return 0;
}
如果lambda表达式没有参数,仍然要提供空括号,就像无参数方法一样:
() -> {for(int i = 100 ; i >= 0 ; i--) System.out.println(i);}
如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型:
Comparator<String> comp
=(first,second)
->first.length() - second.length();
编译器可以推导出first和second必然是字符串
如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号:
ActionListener listener = even ->
System.out.println("The time is" + new Date());
无需指定lambda表达式的返回类型,lambda表达式的返回类型总是会由上下文推导得出
3.函数式接口:
推荐博文:https://blog.csdn.net/lz710117239/article/details/7619262
对于只有一个抽象方法的接口,需要这种的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口
也就是说有且仅有一个抽象方法,但是Object的public方法除外
例如Comparator就是只有一个抽象方法的接口,所以可以提供一个lambda表达式:
Arrays.sort(words ,
( first , second ) -> first.length() - second.length());
在底层,Arrays.sort方法会接收实现了Comparator<String>放某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式
Java API在java.util.function包中定义了很多非常通用的函数式接口,比如一个接口BiFunction<T,U,R>描述了参数类型为T和U而且返回类型为R的函数
可以把我们的字符串比较lambda表达式保存在这个类型的变量中:
BiFunction<String,String,Integer> comp
= (first,second) ->first.length() - second.length();
4.方法引用:
有时,可能已经有现成的方法可以完成你想要传递到其他代码的某个动作,这个时候可以直接引用
例如:
Arrays.sort(strings,String::compareToIgnoreCase);
要用::操作符分隔方法名和对象或类名,主要有三种情况:
object::instanceMethod
Class::staticMethod
Class::instanceMethod
前2中情况中,方法引用等价于提供方法参数的lambda表达式,例如:System.out::println等价于x->System.out.println(x)类似地,Math::pow等价于(x,y)->Math.pow(x,y)
对于第3种情况,第一个参数会成为方法目标。例如:String::compareToIgnoreCase等同于(x,y)->x.compareToIgnoreCase(y)
类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例
5.构造器引用:
构造器引用和方法引用很相似,只不过方法名为new,例如Person::new
也可以用数组类型建立构造器引用,例如:int[]::new
Java有个限制,无法构造泛型类型T的数组,这个时候构造器引用就很有用:
Person[] people = stream.toArray(Person[]::new);
6.变量作用域:
例如:
public static void repeatMessage(String text,int delay){
ActionListener listener = event->{
System.out.println(text);
Toolkit.getDefaultToolkit().deep();
};
new Timer(delay,listener).start();
}
此时lambda表达式中的text变量并不是在这个lambda表达式中定义的
lambda表达式可能会在repeatMessage调用返回很久以后才运行,那个时候这个参数变量可能已经不存在了
所以如何保留text呢?
关于代码块以及自由变量值有一个术语:闭包;在Java中,lambda表达式就是闭包
lambda表达式可以捕获外围作用域中变量的值,只能引用值不会改变的变量,例如下面的做法是不合法的:
public static void countDown(int start,int delay){
ActionListener listener = event->{
start--;
System.out.println(start);
};
new Timer(delay,listener).start();
}
因为如果在lambda表达式中改变变量,并发执行多个动作时就会不安全
另外如果在lambda表达式中引用变量,而这个变量在外部可能改变,这也是不合法的
总结规则:
lambda表达式中捕获的变量必须实际上是最终变量
7.处理lambda表达式:
常用函数式接口:
函数式接口 参数 返回类型 抽象方法名 描述 其他方法
Runnable 无 void run 作为无参数或返回值的动作运行
Supplier<T> 无 T get 提供一个T类型值
Consumer<T> T void accept 处理一个T类型的值 andThen
BiConsumer<T,U> T,U void accept 处理T和U类型的值 andThen
Function<T,R> T R apply 有一个T类型参数的函数
compose,andThen,identity
BiFunction<T,U,R> T,U R apply 有一个T和U类型参数的函数 andThen
UnaryOperator<T> T T apply 类型T上的一元操作符 compose,andThen,identity
BinayOperator<T> T,T T apply 类型T上的二元操作符 andThen,maxBy,minBy
Predicate<T> T boolean test 布尔值函数 and,or,negate,isEqual
BiPredicate<T,U> T,U boolean test 有两个参数的布尔值 and,or,negate
基本类型的函数式接口:
函数式接口 参数类型 返回类型 抽象方法名
BooleanSupplier none boolean getAsBoolean
PSupplier none p getAsP
PConsumer p void accept
ObjPConsumer<T> T,p void accept
PFunction<T> p T apply
PToQFunction p q applyAsQ
ToPFunction<T> T p applyAsP
ToPBiFunction<T,U> T,U p applyAsP
PUnaryOperator p p applyAsP
PBinayOperator p,p p applyAsP
PPredicate p boolean test
如果设计你自己的接口,其中只有一个抽象方法,可以用@FunctionalInterface注解来标记这个接口
这样做有两个优点,如果无意中增加了另一个非抽象方法,编译器会产生一个错误消息,另外javadoc页里会指出你的接口是一个函数式接口