JavaLambda表达式详解
Lambda 表达式是⼀个可传递的代码块,可以在以后执⾏⼀次或多次。
语法
表达式形式:参数,箭头(->),以及⼀个表达式。例如:
(String first, String second) -> first.length() - second.length()
如果代码要完成的计算⽆法放在⼀个表达式中,就可以像写⽅法⼀样,把这些代码放在{}中,并包含显⽰的return语句。例如:(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必然是字符串,因为这个lambda表达式将赋给⼀个字符串⽐较器。
如果⽅法只有⼀个参数,⽽且这个参数的类型可以推导得出,那么甚⾄还可以省略⼩括号:
ActionListener listener = event -> System.out.println(“test”);
⽆须指定lambda表达式的返回类型。lambda表达式的返回类型总是会由上下⽂推导得出。例如:
(String first, String second) -> first.length() - second.length()
可以在需要int类型结果的上下⽂中使⽤。
注释:如果lambda表达式只在某些分⽀返回⼀个值,⽽另外⼀些分⽀不返回值,这是不合法的。例如:
(int x) -> { if (x >= 0) return 1; }//不合法
函数式接⼝
对于只有⼀个抽象⽅法的接⼝,需要这种接⼝的对象时,就可以提供⼀个lambda表达式,这种接⼝成为函数式接⼝(functional interface)。例如:
Arrays.sort(words,(first, second) -> first.length() - second.length());
在底层,Arrays.sort ⽅法会接受实现了Comparator 的某个类的对象。在这个对象上调⽤compare⽅法会执⾏这个lambda表达式的体。
注释:最好把lambda表达式看作是⼀个函数,⽽不是⼀个对象,另外要接受lamda表达式可以传递到函数式接⼝。实际上在Java中,对lambda表达式所能做的也只是转换为函数式接⼝,Java设计者没有为Java语⾔增加函数类型(在其他程序设计语⾔中,可以声明函数类型的变量)。不能把lambda表达式赋值给类型为Object的变量,因为Object不是函数式接⼝。
java.util.function 包中定义了很多⾮常通⽤的函数式接⼝。例如:
Predicate:
public interface Predicate<T> {
boolean test(T t);
//additional default and static methods
}
ArrayList类中有⼀个removeIf⽅法,它的参数就是⼀个Predicate。这个接⼝专门⽤来传递lambda表达式。例如,下⾯的语句将从⼀个数组列表中删除所有的null值:
Supplier:
public interface Supplier<T> {
T get();
java的tostring方法}
供应者(supplier)没有参数,调⽤时会⽣成⼀个T类型的值。供应者⽤于实现懒计算:例如:
LocalDate hireDay = quireNonNullOrElse(day, new LocalDate(1970,1,1));
这不是最优的,我们与day很少为null,所以希望只在必要时才构造默认的LocalDate,通过使⽤供应者,我们就能延迟这个计算:LocalDate hireDay = quireNonNullOrElseGet(day, () -> new LocalDate(1970,1,1));
requireNonNullOrElseGet ⽅法只在需要值时才调⽤供应者。
⽅法引⽤
有时,lambda表达式涉及⼀个⽅法。例如:
var timer = new Timer(1000, event -> System.out.println(event));
但是,如果直接把println⽅法传递到Timer构造器就更好了。具体做法如下:
var timer = new Timer(1000,System.out::println);
表达式System.out::println是⼀个⽅法引⽤(method reference),他只是编译器⽣成⼀个函数式接⼝的实例,覆盖整个接⼝的抽象⽅法来调⽤给定的⽅法。在这个例⼦中,会⽣成⼀个 ActionListener,它的 actionPerformed(ActionEvent e) ⽅法要调⽤
System.out.println(e)。
注释:类似于lambda表达式,⽅法引⽤也不是⼀个对象。不过,为⼀个类型为函数式接⼝的变量赋值时会⽣成⼀个对象。
再来看⼀个例⼦,假设想对字符串进⾏排序,⽽不考虑字母的⼤⼩写,可以传递以下⽅法表达式:
Arrays.sort(strings,String::compareToIgnoreCase)
⼩结:要⽤::运算符分割⽅法与对象或者类名。主要有3种情况:
1. object::instanceMethod
2. Class::instanceMethod
3. Class::staticMethod
在第1种情况下,⽅法引⽤等价于向⽅法传递参数的lambda表达式。对于System.out::println,对象是 System.out,所以⽅法表达式等价于 x -> System.out.println(x)。
对于第2种情况,第1个参数会成为⽅法的隐式参数。例如,String::compareToIgnoreCase
等同于(x, y) -> xpareToIgnoreCase(y)。
在第3种情况下,所有参数都会传递到静态⽅法:Math::pow等价于(x, y) -> Math.pow(x, y)
注:只有当lambda表达式的体只调⽤⼀个⽅法⽽不做其他操作时,才能把lambda表达式重写为⽅法引⽤。考虑以下表达式:
s -> s.length == 0
这⾥有⼀个⽅法调⽤。但是还是有⼀个⽐较,所以这⾥不能使⽤⽅法引⽤。
构造器引⽤
构造器引⽤与⽅法引⽤很类似,只不过⽅法名为new。例如,Person::new 是 Person 构造器的⼀个引⽤。
可以⽤数组类型简历构造器引⽤。例如,int[]::new是⼀个构造器引⽤,他有⼀个参数:即数组的长度。这等价于lambda表达式x -> new
int[x]。
Java有⼀个限制,⽆法构造泛型类型T的数组。但是利⽤数组构造器可以克服这个限制,例如:
Person[] people = Array(Person[]::new);
toArray⽅法调⽤这个构造器来得到⼀个有正确类型的数组。然后填充并返回这个数组。
变量作⽤域
lambda 表达式有3个部分:
1. ⼀个代码块;
2. 参数;
3. ⾃由变量的值,这⾥指⾮参数⽽且不在代码中定义的变量
lambda表达式中访问外围⽅法或者类中的变量。lambda表达式中捕获的变量必须实际上是事实最终变量(effectively final),事实最终变量是指,这个变量初始化之后就不会再为它赋新值。在下⾯例⼦中,text总是指⽰同⼀个String对象,所以捕获这个变量是合法的。
public static void repeatMessage(String text){
ActionListener listener = event -> {
System.out.println(text);
}
}
在lambda表达式中,只能引⽤值不会改变的变量(因为,如果在lambda表达式中更改变量,并发执⾏多个动作是就会不安全),下⾯这种做法是不合法的:
public static void countDown(int start){
ActionListener listener = event -> {
start--; //ERROR:Can’t mutate captured variable
System.out.println(start);
}
}
如果在lambda表达式中引⽤⼀个变量,⽽这个变量可能在外部改变,这也是不合法的。例如:
public static void repeat(String text, int count){
for(int i = 1; i <= count; i++){
ActionListener listener = event -> {
//i的值会改变,因此不能捕获i
System.out.println(String.valueOf(i) + text);
}
}
注:
}
1. lambda表达式的体与嵌套块有相同的作⽤域。所以这⾥同样适⽤命名冲突和遮蔽的有关规则。在lambda表达式中声明与⼀个局部变
量同名的参数或局部变量是不合法的。
2. 在⼀个lambda表达式中使⽤this关键字时,是指创建这个lambda表达式的⽅法的this参数
public class Application{
public void init(){
ActionListener listener = event -> {
System.out.String());
}
}
}
表达式String()会调⽤Application对象的toString⽅法,⽽不是ActionListener实例的⽅法。
处理lambda表达式
如何编写⽅法处理lambda表达式?,使⽤lambda表达式的重点是延迟执⾏
1. 需要选择⼀个合适的函数式接⼝
2. 可以选择⾃定义函数式接⼝,建议使⽤@FunctionalInterface注解标记这个接⼝,这样如果⽆意中增加了另⼀个抽象⽅法,编译器就会
产⽣⼀个错误消息
再谈Comparator
Comparator接⼝包含很多⽅便的静态⽅法来创建⽐较器,这些⽅法可以⽤于lambda表达式或⽅法引⽤。
参考资料
Core Java Volume I — Fundamentals (Eleventh Edition)
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论