介绍
Lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。在其他语言中,可以直接处理代码块,但是Java设计者因为不想随意添加特性让Java变得无法管理所以一直拒绝添加这个特性。知道现在Java SE8,设计者们便引入了Lambda表达式来支持函数式编程处理代码块了。
语法
先举一个栗子,如果我们想传入代码来检查一个字符串是否比另一个字符串短时,我们可以这样做:
(String first, String second) -> first.length() - second.length()
这就是一个lambda表达式。lambd表达式就是一个代码块,以及必须传入代码的变量规范。它的形式是:参数,箭头(->)以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{}并包含显式的return语句。
//例如上面的栗子,短返回-1,长返回1,否则返回0(String first, String second) -> {if(first.length() < second.length()) return -1;else if(first.length() > second.length()) return 1;else return 0;}//如果lambda表达式没有参数,仍然要写空括号() -> {return "hello";}//如果可以根据上下文推导出参数的类型,也可以忽略其类型Comparator<String> comparator = (first,second) -> first.length() - second.length();//如果只有一个参数,而且参数类型可以推导出来,甚至可以省略小括号ActionListener listener = event -> System.out.println("hello"); //代替 (event) 或 (ActionEvent event)
需要知道是,我们无需指定lambda表达式的返回类型。lambda表达式的返回类型总是会由上下文推导得出。
注意:如果一个lambd表达式只在某些条件下返回值,某些不返回值,这时不合法的。例如,(int x) - > {if(x>=0)return 1;} 就不合法
函数式接口
介绍
在Java中已经有很多封装代码块的接口,如ActionListener或Comparator。lambda表达式与这些接口是兼容的。
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口(functional interface)。
规范
- FunctionalInterface注解标注一个函数式接口,不能标注类,方法,枚举,属性这些。
- 如果接口被标注了
@FunctionalInterface,这个类就必须符合函数式接口的规范 - 即使一个接口没有标注
@FunctionalInterface,如果这个接口满足函数式接口规则,依旧被当作函数式接口
重写Object中的方法,不会计入接口方法中,除了final不能重写的,Object中所能重写的方法,写到接口中,不会影响函数式接口的特性 Java8 允许接口中含有非抽象方法,这种在接口中使用default修饰的非抽象方法称为默认方法,默认方法也不会影响函数式接口的特性。我们依然可以认为这是一个函数式接口。
public interface Test {void get();}
使用
下面看一个栗子,在Arrays.sort方法里,第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式:
Arrays.sort(words, ((first, second) -> first.length() - second.length()))
在底层,Arrays.sort方法会接受实现了Comparator
实际上,在Java中,对lambda表达式所能做的也只是能转换为函数式接口。
方法引用
介绍
方法引用(method reference)是用来直接访问类或者实例的已经存在的方法或者构造方法。方法引用提供了一种引用而不执行方法的方式,它需要由兼容的函数式接口构成的目标类型上下文。计算时,方法引用会创建函数式接口的一个实例。
当Lambda表达式中只是执行一个方法调用时,不用Lambda表达式,直接通过方法引用的形式可读性更高一些。方法引用是一种更简洁易懂的Lambda表达式。
表达式 System.out::println 是一个方法引用,它等价于 x -> System.out.println(x)
作用
方法引用的唯一用途是支持Lambda的简写。
方法引用提高了代码的可读性,也使逻辑更加清晰。(优点)
使用
用::操作符分隔方法名与对象或类名。主要有三种情况:
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
在前两种情况,方法引用等价于提供方法参数的lambda表达式。对于第三种情况,第一个参数会成为方法的目标。例如,String::compareToIgnoreCase等同于(x,y) -> x.compareToIgnoreCase(y)。
构造器引用
构造器引用与方法引用很类似,只不过方法名为new。例如,Person::new是Person构造器的一个引用。如果有多个构造器,则会取决于上下文来决定使用哪一个构造器。
ArrayList<String> names = ...;Stream<Person> stream = names.stream().map(Person::new);List<Person> people = stream.collect(Collectors.toList());
另外还有个数组引用,可以用数组类型建立构造器引用。例如,int[]::new是一个构造器引用,它有一个参数:即数组的长度。这等价于lambda表达式x -> new int[x];
Java有一个限制,无法构造泛型类型T的数组,表达式new T[n]会产生错误,因为这会改为new Object[n]。
假设我们需要一个Person对象数组。Stream接口有一个toArray方法可以返回Object数组:
Object[] people = stream.toArray();
不过用户希望得到的是Person引用数组,流库利用构造器引用解决了这个问题。
Person[] people = stream.toArray(Person[]::new);
变量作用域
首先看一段代码
//lambda表达式里引用了外围的变量text,看似没什么问题public static void repeat(String text, int delay){ActionListener listener = event -> {System.out.println(text);};new Timer(delay, listener).start();}//再看看这段代码public static void count(int start, int delay){//同样地使用外围变量和lambda表达式,现在问题来了ActionListener listener = event -> {//报错!不可以改变外围变量的值start--;System.out.println(start);};new Timer(delay, listener).start();}
lambda表达式可以捕获外围作用域中变量的值。在Java中,要确保所捕获的值是明确定义的,这里有一个重要的限制。在lambda表达式中,只能引用值不会改变的变量。如果不做这个限定,在lambda表达式中改变变量,并发执行多个动作时就会不安全。
另外如果在lambda表达式中引用变量,这个变量可能在外部改变,这也是不合法的。
因此,总结:lambda表达式中捕获的变量必须实际上时最终变量(effectively final),实际上的最终变量是指,这个变量初始化后就不会再为它赋新值。
**
在一个lambda表达式里使用this关键字时,是指创建这个lambda表达式的方法的this参数。例如:
public class Application{public void init(){ActionListener listener = event -> {System.out.println(this.toString());}}}
这里表达式this.toString()会调用Application对象的toString()方法,而不是ActionListener实例的方法。
常用函数式接口
| 函数式接口 | 参数类型 | 返回类型 | 抽象方法名 | 描述 | 其他方法 |
|---|---|---|---|---|---|
| Runnable | 无 | void | run | 作为无参数或返回值的动作运行 | |
| Supplier |
无 | 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 |
| UnayOperator |
T | T | apply | 类型T上的一元操作符 | compose,andThen,identity |
| BinaryOperator |
T,T | T | apply | 类型T上的二元操作符 | andThen,maxBy,minBy |
| Predicate |
T | boolean | test | 布尔值函数 | and,or,negate,isEqual |
| BiPredicate |
T,U | boolean | test | 有两个参数的布尔值函数 | and,or,negate |
基本类型的函数式接口
| 函数式接口 | 参数类型 | 返回类型 | 抽象方法名 |
|---|---|---|---|
| BooleanSupplier | none | boolean | getAsBoolean |
| _P_Supplier | none | p | getAsP |
| _P_Consumer | p | void | accept |
| Obj_P_Consumer |
T,p | void | accept |
| _P_Function |
p | T | apply |
| _P_To_Q_Function | p | q | applyAsQ |
| To_P_Function |
T | p | applyAsP |
| To_P_BiFunction |
T,U | p | applyAsP |
| _P_UnaryOperator | p | p | applyAsP |
| _P_BinaryOperator | p,p | p | applyAsP |
| _P_Predicate | p | boolean | test |
注:p,q为int,long,double; P,Q为Int,Long,Double
