为什么叫λ?这个字符是小写的Λ。而Λ有自由变量的含义。

概念

前言

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
在Java中传递一个代码段并不容易,因为Java是一个面向对象的语言,所以必须构造一个对象,这个对象的类需要一个方法包含所需的代码块。
在其他某些语言中,可以直接处理代码块。Java设计者很长时间以来一直拒绝增加这个特性,因为Java的强大之处就在于其简单性和一致性。倘若只要有一个特性能使代码稍整洁一些,就将其加入到语言中,那么这个语言很快就失去其简单性,各种功能耦合在一起,一团糟。
就现在来说,问题已经不是是否增强Java来支持函数式编程(其中有用到lambda表达式),而是如何做到这一点。使其融合在Java中的同时保持Java的简单一致性。

语法

示例

(String a, String b) -> a.length() - b.length()

值得注意的点有:

  • 如果可以推到出一个lambda表达式的参数类型,则可以省略其类型。

    Comparator<String> comp = (a,b) -> a.length() - b.length();
  • 如果方法只有一个参数,而且这个参数的类型可以推导得出,则这个参数还可以省略小括号。

    ActionListener listener = event -> System.out.println("hello");
  • 无需指定lambda表达式的返回类型,这个返回类型通常会由上下文推导得出(函数式接口限定范围,实际表达式给出)。根本原因在于,在Java中lambda表达式是伴随着函数式接口一同使用的。

    (String a, String b) -> a.length() - b.length();

函数式接口(重要)

在Java中,lambda表达式和函数式接口密切相关。
对于只有一个抽象方法的接口,可以将其称为函数式接口。通常还需一个注解@FunctionalInterface
在Java中,lambda表达式的完整定义为:一个可以用于生成一个实现了某个函数式接口对象的工具。
从实际代码中可以看作,lamda表达式可以转换为一个实现了某接口的对象。

正因为函数式接口有且仅有一个抽象方法,lambda表达式不需要指定具体方法,所以有了匿名函数的意义。

方法引用

概念

函数式接口除了使用lambda表达式可以快速实现以外,还支持使用方法引用这个方法。其中构造器引用可以看作方法引用的一个特例。
类似于lambda表达式,方法引用不是一个对象,它在为一个类型为函数式接口的变量赋值时会生成一个对象。
以下两个代码是等价的。

// 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

    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

    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

    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构造器的一个引用。哪个构造器呢?这取决于上下文。

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表达式中。之所以希望以后再执行代码的原因有:

  • 在一个单独的线程中运行代码
  • 多次运行代码
  • 在算法的适当位置运行代码
  • 在发生某个回调时调用代码
函数式接口参数类型返回类型抽象方法名描述其他方法
Runnablevoidrun作为无参数和返回值的动作运行
SupplierTget提供一个T类型的值
ConsumerTvoidaccept处理一个T类型的值andThen
BiConsumer<T,U>T,Uvoidaccept处理一个T类型和一个U类型的值andThen
Function<T,R>TRapply一个T类型参数并返回R类型值的函数compose,andThen,identity
BiFunction<T,U,R>T,URapply两个T类型参数并返回R类型值的函数andThen
UnaryOperatorTTapply类型T的一元操作符compose,andThen,identity
BinaryOperatorT,TTapply类型T的二元操作符andThen,maxBy,minBy
PredicateTvoidtest一个输入参数的布尔值函数and,or,negate,isEqual
BiPredicate<T,U>T,Uvoidrun两个输入参数的布尔值函数and,or,negate

最好使用如上所示的接口(或针对基本类型的,以上的特殊化变种接口)。

最后修改:2020 年 11 月 23 日
如果觉得我的文章对你有用,请随意赞赏