“lambda表达式是一个可传递的代码块,可以在以后执行一次或多次”
//表达式形式:参数、箭头(->)、表达式
(String first, String secod) -> {
return sirst.length() - second.length();
}
- 即使lambda表达式没参数,仍然需要提供空括号,就像无参数方法一样
- 如果可以推导出一个lambda表达式的参数类型,则可以忽略其类型
- 如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至可以省略小括号
- 如果一个lambda表达式只在某些分支返回一个值,而在另外一些分支不返回值,这是不合法的
函数式接口
Java中已经有很多封装代码的接口,对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式,这种接口称为函数式接口(functional interface)
注意:为什么函数式接口必须有一个抽象方法,不是接口中的所有方法都是抽象的,接口完全有可能重新声明Object类的方法,如toString或clone,这些声明可能会让方法不再是抽象的。(Java API中的一些接口会重新声明Object方法来附加javaoc注释),更重要的是在Java SE8中接口可以声明抽象方法。
//Comparator就是只有一个方法的接口 lambda表达式为
Array.sort(words, (first, second) -> first.length() - second.length());
最好将lambd表达式看作是一个函数,而不是一个对象,另外要接受lambd表达式可以传递到函数式接口。在Java中lambd表达式所能做的也只是转换为函数式接口。
方法引用
System.out::println是一个方法引用(method reference),等价于 x -> System.out.println(x)
假设你想对字符串排序,而不考虑字母的大小写,可以传递以下方法表达式:
Arrays.sort(strings, String::compareToIgnoreCase);
要用::操作符分割方法名雨对象或类名,主要有三种情况:
- Object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
在前两种情况中,方法引用等价于提供方法参数的lambda表达式
Math::pow 等价于 (x, y) -> Math.pow(x, y)
System.out::println 等价于 x -> System.out.println(x)
第三种情况,第一个参数会成为方法的目标
例如:String::compareTolgnoreCase 等价于
(x, y) -> x.compareTolgnoreCase(y)
注意:如果有多个同名的重载方法,编译器会尝试从上下文中找出指的哪一个方法。类似于lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例
this参数:可以在方法中引用this参数。例如:this::equals 等同于 x -> x.equals(x);也可以使用super。例如:super::instanceMethod
构造器引用
构造器引用与方法引用类似,不过是方法名为new。例如:Person::new 是Person构造器的一个引用,具体是哪个构造器取决于上下文。
用数组类型建立构造器引用:
例如:int[]::new 是一个构造器引用,他有一个参数:即数组的长度,等价于lambda表达式 x -> new int[x]
可以用数组构造器构造范类型数组:
表达式 new T[n] 会产生错误,因为这会改为 new Object[n]。假设我们需要一个Person对象数组,Stream接口有一个toArray 方法可以返回 Object 数组:Object[] perople = stream.toArray(); 但是返回的是Object 类型的引用数组而不是 Perple的。
使用数组构造器可以解决此问题:
Person[] people = stream.toArray(Person[]::new);
**
变量作用域
lambda表达式有三个部分:
- 一个代码块;
- 参数
- 自由变量的值,指非参数而且不在代码中定义的变量
在lambda表达式中可以访问外围方法或类中的变量(自由变量),它会被lambda表达式捕获(captured)【被lambda表达式的数据结构存储自由变量的值】
可以把一个lambda表达式转换为一个包含方法的对象,这样自由变量的值就会复制到这个对象的实例变量中(在Java中,lambda表达式就是闭包)
lambda表达式可以捕获外围作用域中变量的值,但是只能引用值不会被改变的变量。如果在lambda表达式中改变变量,并发执行多个动作时会不安全。如果这个变量可能在外部被改变,也是不合法的。lambda表达式中捕获的变量,必须实际上是最终变量,此变量初始化后就不会再为它赋新值。再lambda表达式中声明与一个局部变量同名的参数或局部变量也不合法的。
在lambda表达式中使用this关键字时,是指创建这个lambda表达式的方法的this参数
如何处理lambda表达式?
使用lambda表达式的重点是延迟执行(deferred executio)如果想要立即执行代码,完全可以直接执行,而无需把他们包装在一个lambd表达式中。
之所以希望以后再执行代码原因:
- 在一个单独的线程中运行代码
- 多次运行代码
- 在算法的适当位置运行代码(例如:排序中的比较操作)
- 发生某种情况时执行代码(如,点击了一个按钮、数据到达,等)
- 只在必要时才运行代码
常用函数式接口
函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
---|---|---|---|---|---|
Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
Supplicr |
无 | T | get | 提供一个T类型的值 | |
Consumer |
T | void | accept | 处理一个T类型的值 | andThen |
BiConsumer |
T.U | void | accept | 处理T类型和U类型的值 | andThen |
Function |
T | R | apply | 有一个T类型参数的函数 | compose,andThen,identity |
BiFunction |
T, U | R | apply | 有T和U类型参数的函数 | andThen |
UnaryOperator |
T | T | apply | 类型T上的一元操作符 | compose, andThen, identity |
BinaryOpertor |
T, T | T | apply | 类型T上的二元操作符 | andThen,maxBy, minBy |
Predicate |
T | boolean | test | 布尔值函数 | and,or, negate,isEqual |
BiPredicate |
T, U | boolean | test | 有两个参数的布尔值函数 | and,or,negate |
注意:如果是自己设计的接口,其中只有一个抽象方法,可以用@FunctionalInterface 注释来标记这个接口。这样做有两个优点:1.如果五一增加了另一个非抽象方法,编译器会产生一个错误消息。2.javadoc页里会指出此接口是一个函数式接口