Loading... > 为什么叫λ?这个字符是小写的Λ。而Λ有自由变量的含义。 # 概念 ## 前言 lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。 在Java中传递一个代码段并不容易,因为Java是一个面向对象的语言,所以必须构造一个对象,这个对象的类需要一个方法包含所需的代码块。 在其他某些语言中,可以直接处理代码块。Java设计者很长时间以来一直拒绝增加这个特性,因为Java的强大之处就在于其简单性和一致性。倘若只要有一个特性能使代码稍整洁一些,就将其加入到语言中,那么这个语言很快就失去其简单性,各种功能耦合在一起,一团糟。 就现在来说,问题已经不是是否增强Java来支持函数式编程(其中有用到lambda表达式),而是如何做到这一点。使其融合在Java中的同时保持Java的简单一致性。 ## 语法 示例 ```java (String a, String b) -> a.length() - b.length() ``` 值得注意的点有: * 如果可以推到出一个lambda表达式的`参数类型`,则可以省略其类型。 ```java Comparator<String> comp = (a,b) -> a.length() - b.length(); ``` * 如果方法只有一个参数,而且这个参数的类型可以推导得出,则这个参数还可以省略小括号。 ```java ActionListener listener = event -> System.out.println("hello"); ``` * 无需指定lambda表达式的返回类型,这个返回类型通常会由上下文推导得出(函数式接口限定范围,实际表达式给出)。根本原因在于,在Java中lambda表达式是伴随着函数式接口一同使用的。 ```java (String a, String b) -> a.length() - b.length(); ``` ---------- # 函数式接口(重要) 在Java中,lambda表达式和函数式接口密切相关。 对于只有一个抽象方法的接口,可以将其称为函数式接口。通常还需一个注解@FunctionalInterface 在Java中,lambda表达式的完整定义为:`一个可以用于生成一个实现了某个函数式接口对象的工具。` 从实际代码中可以看作,lamda表达式可以转换为一个实现了某接口的对象。 > 正因为函数式接口有且仅有一个抽象方法,lambda表达式不需要指定具体方法,所以有了匿名函数的意义。 ---------- # 方法引用 ## 概念 函数式接口除了使用lambda表达式可以快速实现以外,还支持使用`方法引用`这个方法。其中`构造器引用`可以看作方法引用的一个特例。 类似于lambda表达式,方法引用不是一个对象,它在为一个类型为函数式接口的变量赋值时会生成一个对象。 以下两个代码是等价的。 ```java // lambda表达式实现了一个ActionListener接口中的actionPerformed(ActionEvent e)。 var timer = new Timer(1000, event -> System.out.println(event)); var timer = new Timer(1000, System.out::println); ``` > 注意:只有当lambda表达式的代码块只调用一个方法而不做其他操作时,才能把lambda表达式重写为方法引用。 ## 使用 方法引用要通过`::`分割方法名与类名或对象。主要有三种情况。 * object::instanceMethod ```java public class Test2 { public static void main(String[] args) { String[] str = {"aa", "ddd", "dddf", "sdddss", "sdfsgsfssds"}; String str2 = "aa"; // 这里的str2 和 System.out 都是实例对象 // 要注意的是:str2::equals第一个参数会成为方法的隐式参数。 // (x,) -> x.equals(y) Arrays.stream(str).filter(str2::equals).forEachOrdered(System.out::println); } } // 输出: aa ``` * Class::instanceMethod ```java public class Test2 { public static void main(String[] args) { String[] str = {"a", "bb", "ccc", "dddd", "eeeee"}; // 这里等价 (x,y) -> String.compareTo(x,y) Arrays.stream(str).sorted(String::compareTo).forEachOrdered(System.out::println); } } ``` * Class::staticMethod ```java public class Test2 { public static void main(String[] args) { Integer[] i = {1, 2, 3}; // Integer::valueOf是静态方法 // 等价 x -> Integer.valueOf(x) System.out.println(Arrays.stream(i).mapToInt(Integer::valueOf).sum()); } } ``` ### 构造器引用 构造器引用就是一种特殊的方法引用,只不过方法名为new.例如Person::new 是Person构造器的一个引用。哪个构造器呢?这取决于上下文。 ```java class Person { String name; int age; public Person(String name) { this.name = name; this.age = 20; } @Override public String toString() { return "[ name: " + name + ", age: " + age + "]"; } } public class Test2 { public static void main(String[] args) { String[] names = {"dd", "ff", "ss"}; Arrays.stream(names).map(Person::new).forEach(System.out::println); } } 输出: [ name: dd, age: 20] [ name: ff, age: 20] [ name: ss, age: 20] ``` ---------- # 接收lambda表达式的标准接口 前面所说都是关于如何生成lambda表达式和如何把lmabda表达式传递到一个函数式接口的方法,同时介绍了等价于lambda表达式的方法引用。 下面来看如何编写方法处理lambda表达式。 使用lambda表达式的重点是`延迟执行deferred execution`。毕竟,如果想要立即执行代码,完全可以直接执行,无需包装在一个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类型参数并返回R类型值的函数 | compose,andThen,identity | | BiFunction<T,U,R> | T,U | R | apply | 两个T类型参数并返回R类型值的函数 | andThen | | UnaryOperator<T> | T | T | apply | 类型T的一元操作符 | compose,andThen,identity | | BinaryOperator<T> | T,T | T | apply | 类型T的二元操作符| andThen,maxBy,minBy | | Predicate<T>| T | void | test | 一个输入参数的布尔值函数 |and,or,negate,isEqual | | BiPredicate<T,U>| T,U | void | run | 两个输入参数的布尔值函数 | and,or,negate | 最好使用如上所示的接口(或针对基本类型的,以上的特殊化变种接口)。 最后修改:2020 年 11 月 23 日 06 : 19 PM © 允许规范转载