# 语法

(参数) -> {表达式} ,形如:

(String first, String second) -> first.length() - second.length()

# 函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口。

lambda 表达式可以看做一个函数,并且 lambda 表达式可以传递到函数式接口。如下的 Person 接口:

interface Person {
    public void run(String name);
}

可以使用 lambda 表达式实现接口的方法:

Person person = name -> System.out.println(name + " is running.");

# 方法引用

上述 lambda 表达式也可以传递一个方法的引用:

Person person = System.out::println;

有三种方法引用的情况:

  • object::instanceMethod

传递一个对象的方法。

package com.example.demo.test;
public class LambdaTest {
    public static void main(String[] args) {
        Woman woman = new Woman();
        Person person = woman::run;
        person.run("huihui");
    }
}
interface Person {
    public void run(String name);
}
class Woman {
    public void run(String name) {
        System.out.println(name + "在为减肥跑步。");
    }
}

运行结果:

huihui在为减肥跑步。
  • Class::staticMethod

传递一个类的静态方法。

Person person = System.out::println;
  • Class::instanceMethod

传递一个类的实例方法。第一个参数会成为方法的目标,例如, String::compareToIgnoreCase 等同于 (x, y) -> x.compareToIgnoreCase(y)

package com.example.demo.test;
public class LambdaTest {
    
    public static void main(String[] args) {
        Person person = Woman::run;
        Woman woman = new Woman();
        person.run(woman,"huihui");
    }
}
interface Person {
    
    public void run(Woman woman, String name);
}
class Woman {
    public void run(String name) {
        System.out.println(name + "在为减肥跑步。");
    }
}

运行结果:

huihui在为减肥跑步。

方法引用中,使用 this 或者 super 也是合法的。

package com.example.demo.test;
public class LambdaTest{
    public static void main(String[] args) {
        RunningWomen runningWomen = new RunningWomen();
        Person superPerson = runningWomen.createSuperPerson();
        Person thisPerson = runningWomen.createThisPerson();
        superPerson.run("huihui");
        thisPerson.run("huihui");
    }
}
interface Person {
    public void run(String name);
}
class Woman {
    public void run(String name) {
        System.out.println(name + "在为减肥跑步。");
    }
}
class RunningWomen extends Woman {
    @Override
    public void run(String name) {
        System.out.println(name + "在晨练跑步。");
    }
    public Person createSuperPerson() {
        return super::run;
    }
    public Person createThisPerson() {
        return this::run;
    }
}

运行结果:

huihui在为减肥跑步。
huihui在晨练跑步。

# 构造器引用

可以应用一个对象的构造器,将一个变量转化为其他类型的变量。

package com.example.demo.test;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LambdaTest{
    public static void main(String[] args) {
        List<String> integerList = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            integerList.add(String.valueOf(i));
        }
        List<Woman> womanList = integerList.stream().map(Woman::new).collect(Collectors.toList());
        womanList.forEach(System.out::println);
    }
}
class Woman {
    private String name;
    public Woman(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "Woman{" +
                "name='" + name + '\'' +
                '}';
    }
}

运行结果:

Woman{name='0'}
Woman{name='1'}
Woman{name='2'}
Woman{name='3'}
Woman{name='4'}

# 变量作用域

在 lambda 表达式中,可以使用表达式之外的变量。

public class LambdaTest {
  public static void main(String[] args) {
      String name = "huihui";
      Person person = () -> System.out.println(name + " is running.");
      person.run();
  }
}
interface Person {
  void run();
}

但是,在 lambda 表达式中只能使用值不会改变的变量。不可以在 lambda 表达式中改变变量的值,因为 lambda 表达式是线程不安全的。如以下写法:

Person person = () -> name = "xixi";

若变量在 lambda 表达式外部发生变化,也是不允许的。 如一下写法:

for (int i = 0; i < 10; i++) {
    Person person = () -> System.out.println(name + " is running." + i);
    person.run();
}

以上均会提示 Variable used in lambda expression should be final or effectively final

在方法中不能声明同名的变量,因此 lambda 表达式中声明与一个局部变量同名的参数或局部变量也是不合法的,如以下写法:

String name = "huihui";
        Person person = name -> System.out.println(name + " is running.");

lambda 表达式中的 this 关键字指的是外部的含义,并没有什么改变。

public class LambdaTest{
    public static void main(String[] args) {
        LambdaTest lambdaTest = new LambdaTest();
        Person person = lambdaTest.createPerson();
        person.run();
    }
    public Person createPerson() {
        return () -> System.out.println(this.getClass().getName());
    }
}
interface Person {
    void run();
}

运行结果:

com.example.demo.test.LambdaTest

this 其实就是 createPerson 方法的 this。

# 处理 lambda 表达式

使用 lambda 表达式的重点是延迟执行,如:

  • 在一个单独的线程中运行代码;
  • 多次运行代码;
  • 在算法的适当位置运行代码(如排序中的比较算法);
  • 发生某种情况时执行代码(如点击按钮等);
  • 只在必要时才运行代码。

以下是常用的函数式接口,在开发过程中,可以使用这些函数式接口。

函数式接口参数类型返回类型抽象方法名描述其他方法
Runnablevoidrun作为无参数或返回值的动作运行
Supplier<T>Tget提供一个 T 类型的值
Consumer<T>Tvoidaccept处理一个 T 类型的值andThen
BigConsumer<T, U>T, Uvoidaccept处理 T 和 U 类型的值andThen
Function<T, R>TRapply有一个 T 类型参数的函数compose, andThen, identity
BigFunction<T, U, R>T, URapply有 T 和 U 类型参数的函数andThen
UnaryOperator<T>TTapply类型 T 上的一元操作符compose, andThen, identity
BinaryOperator<T>T, TTapply类型 T 上的二元操作符andThen, maxBy, minBy
Predicate<T>Tbooleantest布尔值函数and, or, negate, isEqual
BigPredicate<T, U>T, Ubooleantest有两个参数的布尔值函数and, or, negate

以下是基本类型的函数式接口。

函数式接口参数类型返回类型抽象方法名
BooleanSupplierbooleangetAsBoolean
PSupplierpgetAsP
pConsumerpvoidaccept
ObjpConsumer<T>T, pvoidaccept
pFunction<T>pTapply
pToQFunctionpqapplyAsQ
TopFunction<T>TpapplyAsp
TopBiFunction<T, U>T, UpapplyAsp
pUnaryOperatorppapplyAsp
pBinaryOperatorp, ppapplyAsp
pPredicatepbooleantest

注:p, q 为 int , long, double; P, Q 为 Integer, Long, Double

在使用函数式接口时,最好使用上述两个表的接口,要注意根据特定情况使用特定的接口,减少自动装箱。如以下代码,使用 IntConsumer 而不是 Consumer ,因为传入的是 int 类型的参数。

IntConsumer intConsumer = System.out::println;
for (int i = 0; i < 10; i++) {
    intConsumer.accept(i);
}

@FunctionalInterface 注解表示接口是一个函数式接口,自己定义的函数式接口最好使用此注解进行标记,避免无意增加一个非抽象方法。