Skip to content

4. Java 8 lambda 表达式

4.1. 示例-01 - 功能接口与 Lambda 表达式

  

请看以下代码:


package dvp.java8.lambdas;
 
public class Exemple01 {
  public static void main(String[] args) {
 
    // anonymous classes
    I1 ia1 = new I1() {
      @Override
      public void doSomething() {
        System.out.println("ia1.doSomething");
      }
    };
 
    I2 ia2 = new I2() {
      @Override
      public String getSomething(double value) {
        return String.format("ia2.getSomething(%s)", value);
      }
    };
 
    // lambdas
    I1 ib1 = () -> System.out.println("ib1.lambda");
    I2 ib2 = (value) -> String.format("ib2.lambda(%s)", value);
    I1 ib3 = () -> {
      System.out.println("ib3.lambda");
    };
    I2 ib4 = (double value) -> {
      return String.format("ib4.lambda(%s)", value);
    };
 
    // app
    ia1.doSomething();
    System.out.println(ia2.getSomething(4.3));
    ib1.doSomething();
    System.out.println(ib2.getSomething(5.8));
    ib3.doSomething();
    System.out.println(ib4.getSomething(10.1));
  }
}
 
@FunctionalInterface
interface I1 {
  void doSomething();
}
 
@FunctionalInterface
interface I2 {
  String getSomething(double value);
}
  • 第 41–44 行:定义一个函数接口 I1。函数接口是指仅包含一个方法的接口。它并不依赖于第 41 行是否带有 [@FunctionalInterface] 注解,该注解是可选的;
  • 第 46–49 行:第二个函数式接口 I2;
  • 第 6–19 行:接口 I1 和 I2 通过匿名类实现,这是在引入 lambda 表达式之前最常见的解决方案;
  • 第 21–29 行:使用 lambda 表达式实现接口 I1 和 I2;
  • 第 7–12 行:使用匿名类实现接口 I1。使用匿名类实现接口 I 的语法如下:
I i=new I(){
    @Override
    public T1 m1(...){
...
    }
    public T2 m2(...){
...
    }
}

其中 m1、m2、... 是接口 I 定义的方法。

  • 第 14–19 行:使用匿名类实现接口 I2;
  • 第 22 行:使用 lambda 函数实现接口 I1。这里利用了函数接口仅有一个方法这一特性。该 lambda 函数随后实现了这个单一的方法 M。其语法如下:
(T1 param1, T2 param2, ...) -> {implémentation de la méthode M ;}

如果编译器能从上下文中推断出类型(类型推断),则可以省略类型 T1、T2、Tn。

  • 第 22 行:方法 [I1.doSomething] 的实现,其签名如下:

void doSomething();

[doSomething] 是一个无参数且不返回结果的方法。其 lambda 实现可以像第 22 行那样编写,也可以像第 24–26 行那样编写,即可以在 lambda 函数的代码周围添加大括号。如果该代码仅包含一条语句(如本例所示),则可以省略这些大括号;

  • 第 23 行:实现具有以下签名的 [I1.getSomething] 方法:

String getSomething(double value);

[getSomething] 接受一个 [double] 类型的参数,并返回一个 [String] 类型的结果。它的 lambda 实现可以是第 23 行中的,也可以是第 27–29 行中的。在第 23 行的实现中:

  • [value] 参数的类型被省略。此时将使用 [getSomething] 签名中定义的 [double] 类型;
  • lambda 代码未用圆括号包围。此时 lambda 的结果即为该代码中单个表达式的值,即:String.format("ib2.lambda(%s)", value);

在第 27–29 行的实现中:

  • 显式声明了 [value] 参数的类型;
  • 我们使用 [return] 来返回 lambda 的结果。在此情况下,必须使用大括号;
  • 第 32–37 行:我们调用各种匿名函数和 lambda 表达式;

所得结果如下:

1
2
3
4
5
6
ia1.doSomething
ia2.getSomething(4.3)
ib1.lambda
ib2.lambda(5.8)
ib3.lambda
ib4.lambda(10.1)

4.2. 示例-02 - Predicate<T> 函数接口

  

大多数情况下,我们处理的是库中提供的函数接口,而非自己定义的函数接口。这里,我们关注的是 [java.util.function] 包中定义的 [Predicate] 函数接口,该包包含了 Java 8 中大部分的函数接口。其定义如下:

Image

我们曾提到,函数式接口通常只有一个方法。但这里却有四个。Java 8 引入的另一项创新是接口中的默认方法概念,它通过 [default] 关键字进行标记。这里有三个这样的方法。这些方法的特点是具有默认实现。 因此,实现包含默认方法的接口的类无需实现这些方法。所以,想要实现 [Predicate] 接口的类只需实现一个方法:即 [test] 方法。由此可见,[Predicate] 接口确实是一个函数式接口。因此,我们可以定义:函数式接口是指其实现中仅包含一个必选方法的接口。如果它包含多个方法,其余方法必须带有 [default] 关键字。

[Predicate<T>.test] 方法接受类型为 T 的参数,并返回一个布尔值。该接口通常用于过滤集合。为说明其用法,我们将使用以下数据:


package dvp.data;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
public class Personne {
 
  public enum Sexe {HOMME,FEMME};
 
  // data
  private String nom;
  private int age;
  private double poids;
  private Sexe sexe;

  // manufacturers
  public Personne() {
 
  }
 
  public Personne(String nom, Sexe sexe, int age, double poids) {
    this.nom = nom;
    this.sexe=sexe;
    this.age = age;
    this.poids = poids;
  }
 
  // getters and setters
...
 
  // toString
  @Override
  public String toString(){
    try {
      return new ObjectMapper().writeValueAsString(this);
    } catch (JsonProcessingException e) {
      return e.getMessage();
    }
  }
}
  • 第 32–38 行:[toString] 方法返回该人的 JSON 字符串;

[People] 类定义了一个包含 3 个人的列表:


package dvp.data;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
 
import java.util.Arrays;
import java.util.List;
 
public class Personnes {
  private static List<Personne> personnes = Arrays.asList(new Personne("jean", Personne.Sexe.HOMME, 20, 70),
    new Personne("marie", Personne.Sexe.FEMME, 10, 30), new Personne("camille", Personne.Sexe.FEMME, 30, 55));
 
  public static List<Personne> get() {
    return personnes;
  }
 
  public static String toString(List<Personne> liste) {
    try {
      return new ObjectMapper().writeValueAsString(liste);
    } catch (JsonProcessingException e) {
      return e.getMessage();
    }
  }
}
  • 第 10–11 行:包含 3 人的列表;
  • 第13–15行:一个用于检索该列表的静态方法;
  • 第17–23行:一个静态方法,用于获取作为参数传递的人员列表的JSON字符串;

以下代码将使用这些数据:


package dvp.java8.lambdas;
 
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;
 
import dvp.data.Personne;
import dvp.data.Personnes;
 
public class Exemple02 {
  public static void main(String[] args) {
    // predicate implemented by anonymous class
    Predicate<Personne> filterPoids=new Predicate<Personne>() {
      @Override
      public boolean test(Personne personne) {
        return personne.getPoids()<50;
      }
    };
// predicate implemented by a lambda
    Predicate<Personne> filterAge = p -> p.getAge() < 28;
    // list of persons
    List<Personne> personnes = Personnes.get();
    // displays
    System.out.println(Personnes.toString(filterPersonnes(personnes, filterAge)));
    System.out.println(Personnes.toString(filterPersonnes(personnes, filterPoids)));
  }
 
  private static List<Personne> filterPersonnes(List<Personne> personnes, Predicate<Personne> filter) {
      // [filter] filters the [people] list
    List<Personne> personnesFiltrées = new ArrayList<>();
    for (Personne p : personnes) {
      if (filter.test(p)) {
        personnesFiltrées.add(p);
      }
    }
    return personnesFiltrées;  }
}
  • 第 13–18 行:使用匿名类实现 Predicate<Person> 接口。这是一个基于人员体重的过滤器;
  • 第 20 行:使用 lambda 表达式实现 Predicate<Person> 接口。这是一个基于人物年龄的过滤器。根据上述内容,也可以这样编写:

        Predicate<Personne> filterAge = (Personne p) -> {
            return (p.getAge() < 28);
        };

但第 20 行中的版本更为简洁。参数 p 的类型由上下文推导得出。这里,我们正在构造类型 [Predicate<Person>]。因此,实现的方法具有签名 [boolean test(Person param)]。所以,第 20 行中 p 的隐式类型是 [Person];

  • 第 22 行:我们获取一个预定义的人员列表;
  • 第 24 行:按年龄对人员进行筛选;
  • 第 25 行:按体重进行筛选。在这两种情况下,我们都会显示筛选后列表的 JSON 字符串;
  • 第 28–37 行:一个静态方法,
    • 该方法的参数包括:待筛选的人员列表以及筛选器。该筛选器是 [Predicate<Person>] 接口的实例。为方便起见,本文档在此及后续内容中,将接口 I 的实例统称为实现 I 的类 C 的实例;
    • 返回过滤后的列表作为结果;
  • 第 32 行:我们使用 [Predicate] 接口的 [test] 方法。根据传递给该方法的过滤器,[test] 方法将:

return personne.getPoids()<50;


return p.getAge() < 28

执行 [Exemple02] 类将得到以下结果:

[{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"},{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}]
[{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}]

4.3. 示例-03 - 函数接口 Function<T,R>

  

函数接口 Function<T,R> 的定义如下:

Image

该接口的唯一方法具有签名 R apply(T t)。它通常用于从 Collection<T> 类型创建新的 Collection<R> 类型。为了说明该接口,我们将使用以下代码:


package dvp.java8.lambdas;
 
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import dvp.data.Personne;
import dvp.data.Personnes;
 
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
 
public class Exemple03 {
  public static void main(String[] args) throws JsonProcessingException {
    // implementation with anonymous class
    Function<Personne, String> mapToName = new Function<Personne, String>() {
      @Override
      public String apply(Personne personne) {
        return personne.getNom();
      }
    };
    // implementation with lambda
    Function<Personne, Integer> mapToAge = p -> p.getAge();
    // list of persons
    List<Personne> personnes = Personnes.get();
    // jSON
    ObjectMapper jsonMapper = new ObjectMapper();
    // displays
    System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
    System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
  }
 
  // transformation List<Person> --> List<T>
  private static <T> List<T> mapPersonnes(List<Personne> personnes, Function<Personne, T> mapper) {
    List<T> maps = new ArrayList<>();
    for (Personne p : personnes) {
      maps.add(mapper.apply(p));
    }
    return maps;
  }
}
  • 第 15–20 行:我们使用一个执行 Person → String 转换的匿名类来实现 [Function<Person, String>] 接口;
  • 第 22 行:我们使用一个执行 Person → Integer 转换的 lambda 表达式来实现 [Function<Person, Integer>] 接口;
  • 第 33–39 行:一个静态方法,
    • 该方法接受两个参数:第一个参数类型为 List<Person>,表示待转换的人员列表;第二个参数类型为 Function<Person, T>,表示一个函数,该函数会遍历列表中的每个人,并生成类型为 T 的对象;
    • 返回一个 List<T>,其中每个元素都是 Person -> T 转换的结果;
  • 第 35–37 行:应用 Person -> T 转换。如果方法的第二个参数是 [mapToName] 对象,则执行 Person -> String 转换;如果是 [mapToAge] 对象,则执行 Person -> Integer 转换;

结果如下:

["jean","marie","camille"]
[20,10,30]

4.4. 示例-04 - Consumer<T> 函数接口

  

函数接口 Consumer<T> 的定义如下:

Image

该接口的唯一方法的签名如下:void accept(T t)。该方法处理(消耗)其参数,且不返回任何结果。为说明这一点,我们将使用以下代码:


package dvp.java8.lambdas;
 
import java.util.List;
import java.util.function.Consumer;
 
import dvp.data.Personne;
import dvp.data.Personnes;
 
public class Exemple04 {
    public static void main(String[] args) {
        // list of persons
        List<Personne> personnes = Personnes.get();
        // anonymous implementation
        Consumer<Personne> consumerAge = new Consumer<Personne>() {
            @Override
            public void accept(Personne personne) {
                System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
            }
        };
        // immplémentaton lambda
        Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());        
        // displays
        for (Personne p : personnes) {
            consumerAge.accept(p);
        }
        System.out.println("--------");
        for (Personne p : personnes) {
            consumerPoids.accept(p);
        }
    }
}
  • 第 14–19 行:接口 [Consumer<Person>] 由一个匿名类实现,该类的 [accept] 方法会显示该人的姓名和年龄;
  • 第 21 行:接口 [Consumer<Person>] 由一个 lambda 函数实现,该函数的隐式 [accept] 方法用于显示该人的姓名和体重;
  • 第 23–25 行:人员列表由 [consumerAge] 的实现进行消费;
  • 第 27–29 行:人员列表由 [consumerWeight] 实现进行消费;

结果如下:

1
2
3
4
5
6
7
 age de jean = 20
 age de marie = 10
 age de camille = 30
--------
 poids de jean = 70.0
 poids de marie = 30.0
poids de camille = 55.0

4.5. 示例-05 - BiConsumer<T,U> 函数接口

  

函数接口 BiConsumer<T,U> 定义如下:

Image

该接口的唯一方法的签名如下:void accept(T t, U u)。它处理类型 T 并使用附加信息 U u。我们将通过以下代码演示其用法:


package dvp.java8.lambdas;
 
import dvp.data.Personne;
import dvp.data.Personnes;
 
import java.util.List;
import java.util.function.BiConsumer;
 
public class Exemple05 {
  public static void main(String[] args) {
    // list of persons
    List<Personne> personnes = Personnes.get();
    // anonymous implementation
    BiConsumer<Personne, Integer> biconsumerAge = new BiConsumer<Personne, Integer>() {
      @Override
      public void accept(Personne personne, Integer integer) {
        personne.setAge(personne.getAge() + integer);
        System.out.printf("age de %s = %s%n", personne.getNom(), personne.getAge());
      }
    };
    // lambda implementation
    BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
      p.setPoids(p.getPoids() + i);
      System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
    };
    // displays
    for (Personne p : personnes) {
      biconsumerAge.accept(p, 100);
    }
    System.out.println("--------");
    for (Personne p : personnes) {
      biconsumerPoids.accept(p, 200);
    }
  }
}
  • 第 14–20 行:使用匿名类实现 BiConsumer<T,U> 接口。[apply] 方法使用其第二个参数来更新作为第一个参数传递的人物的年龄,然后显示结果;
  • 第 22–25 行:使用 lambda 表达式实现 BiConsumer<T,U> 接口。隐含的 [apply] 方法使用其第二个参数来更新作为第一个参数传递的人物的体重。随后显示结果;
  • 第 27–29 行:使用 [biconsumerAge] 实现消耗人员列表;
  • 第 31–33 行:使用 [biconsumerWeight] 实现处理人员列表;

所得结果如下:

1
2
3
4
5
6
7
age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0

4.6. 示例-06 - BiFunction<T,U,R> 函数接口

  

函数接口 BiFunction<T,U,R> 定义如下:

Image

该接口的唯一方法具有以下签名:R apply(T t, U u)。该方法与 [BiConsumer.apply] 方法类似,但后者不返回结果,而 [BiFunction.apply] 方法则会返回结果。我们将通过以下代码演示其用法:


package dvp.java8.lambdas;
 
import java.util.List;
import java.util.function.BiFunction;
 
import dvp.data.Personne;
import dvp.data.Personnes;
 
public class Exemple06 {
    public static void main(String[] args) {
        // list of persons
        List<Personne> personnes = Personnes.get();
        // anonymous implementation
        BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
            @Override
            public Integer apply(Personne personne, Integer integer) {
                return personne.getAge() + integer;
            }
        };
        // lambda implementation
        BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
            return p.getPoids() + i;
        };
        // displays
        for (Personne p : personnes) {
            System.out.printf("age de %s = %s%n", p.getNom(), biFunctionAge.apply(p, 100));
        }
        System.out.println("--------");
        for (Personne p : personnes) {
            System.out.printf("poids de %s = %s%n", p.getNom(), biFunctionPoids.apply(p, 200));
        }
    }
}
  • 第 14–19 行:BiFunction<Person, Integer, Integer> 接口通过匿名类实现。其 [apply] 方法返回作为第一个参数传入的人的年龄,并加上第二个参数的值;
  • 第 21–23 行:使用 lambda 函数实现了 BiFunction<Person, Integer, Double> 接口。[apply] 方法返回将作为第一个参数传递的人的体重增加第二个参数的值后的结果;
  • 第 25–27 行:将 [biFunctionAge] 的实现应用于这些人;
  • 第 29–31 行:将 [biFunctionWeight] 的实现应用于这些人;

所得结果如下:

age de jean = 120
age de marie = 110
age de camille = 130
--------
poids de jean = 270.0
poids de marie = 230.0
poids de camille = 255.0

除了 lambda 函数外,Java 8 还引入了 Stream<T> 类型,它表示由类型 T 的元素组成的流。这些元素可以经过由 lambda 函数实现的连续转换。在可能的情况下,且当存在多个处理器时,这些转换有时可以并行执行。

4.7. 示例-07 - Supplier<T> 函数接口

  

功能接口 Supplier<T> 定义如下:

Image

该接口的唯一方法的签名是:T get(),其作用是返回一个类型为 T 的对象。

我们将通过以下代码来演示这个函数式接口:


package dvp.java8.lambdas;
 
import java.util.List;
import java.util.Random;
import java.util.function.Supplier;
 
import dvp.data.Personne;
import dvp.data.Personnes;
 
public class Exemple07 {
    public static void main(String[] args) {
        // anonymous implementation
        Supplier<Personne> supplier = new Supplier<Personne>() {
            // list of persons
            List<Personne> personnes = Personnes.get();
 
            // interface implementation
            @Override
            public Personne get() {
                int i = new Random().nextInt(personnes.size());
                return personnes.get(i);
            }
        };
        // operation
        for (int i = 0; i < 5; i++) {
            affiche(supplier);
        }
    }
 
    // person display
    public static void affiche(Supplier<Personne> supplier) {
        System.out.println(supplier.get());
    }
}
  • 第 13–28 行:Supplier<Person> 类型的实现;
  • 第 31–33 行:静态方法 [display] 期望接收类型为 Supplier<Person> 的参数;
  • 第 25–27 行:使用 Supplier<Person> 实例;

得到以下结果:

1
2
3
4
5
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}
{"nom":"marie","age":10,"poids":30.0,"sexe":"FEMME"}
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"jean","age":20,"poids":70.0,"sexe":"HOMME"}
{"nom":"camille","age":30,"poids":55.0,"sexe":"FEMME"}