Skip to content

4. Java 8 的 lambda 表达式

4.1. 示例-01 - 函数接口与 lambda 表达式

  

请看以下代码:


package dvp.java8.lambdas;

public class Exemple01 {
  public static void main(String[] args) {

    // 匿名类
    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);
      }
    };

    // lambda表达式
    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);
    };

    // 应用程序
    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 行:接口 I1 和 I2 通过 lambda 函数实现;
  • 第 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};

  // 数据
  private String nom;
  private int age;
  private double poids;
  private Sexe sexe;

  // 构造函数
  public Personne() {

  }

  public Personne(String nom, Sexe sexe, int age, double poids) {
    this.nom = nom;
    this.sexe=sexe;
    this.age = age;
    this.poids = poids;
  }

  // getter 和 setter
...

  // toString
  @Override
  public String toString(){
    try {
      return new ObjectMapper().writeValueAsString(this);
    } catch (JsonProcessingException e) {
      return e.getMessage();
    }
  }
}
  • 第 32-38 行:方法 [toString] 返回该人员的字符串 jSON;

类 [Personnes] 定义了一个包含 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<Personne> filterPoids=new Predicate<Personne>() {
      @Override
      public boolean test(Personne personne) {
        return personne.getPoids()<50;
      }
    };
// 由 lambda 实现的谓词
    Predicate<Personne> filterAge = p -> p.getAge() < 28;
    // 人员列表
    List<Personne> personnes = Personnes.get();
    // 显示次数
    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] 过滤列表 [personnes]
    List<Personne> personnesFiltrées = new ArrayList<>();
    for (Personne p : personnes) {
      if (filter.test(p)) {
        personnesFiltrées.add(p);
      }
    }
    return personnesFiltrées;  }
}
  • 第13-18行:通过匿名类实现Predicate<Personne>接口。这是一个针对人员体重的筛选器;
  • 第20行:通过一个lambda函数实现Predicate<Personne>接口。这是一个基于人员年龄的过滤器。根据上述说明,它也可以按以下方式编写:

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

但第20行的版本更为简洁。参数p的类型由上下文推导得出。 此处构建了一个类型 [Predicate<Personne>]。因此,所实现方法的签名即为 [boolean test(Personne param)]。故第 20 行中 p 的隐式类型即为 [Personne];

  • 第 22 行:获取预定义的人员列表;
  • 第24行:根据年龄对人员进行筛选;
  • 第 25 行:根据体重进行筛选。在这两种情况下,都会显示经过筛选的列表中的字符串 jSON;
  • 第28-37行:一个静态方法,
    • 接受的参数为:待筛选的人员列表和筛选器。该筛选器是接口 [Predicate<Personne>] 的实例。为便于表述,本文档在此及后续内容中,将接口 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 {
    // 使用匿名类实现
    Function<Personne, String> mapToName = new Function<Personne, String>() {
      @Override
      public String apply(Personne personne) {
        return personne.getNom();
      }
    };
    // 使用 lambda 表达式实现
    Function<Personne, Integer> mapToAge = p -> p.getAge();
    // 人员列表
    List<Personne> personnes = Personnes.get();
    // jSON
    ObjectMapper jsonMapper = new ObjectMapper();
    // 显示
    System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToName)));
    System.out.println(jsonMapper.writeValueAsString(mapPersonnes(personnes, mapToAge)));
  }

  // List<Personne> 转换为 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 行:通过一个匿名类实现 [Function<Personne, String>] 接口,该类将执行 Personne 到 String 的转换;
  • 第 22 行:使用一个 lambda 表达式实现 [Function<Personne, Integer>] 接口,该表达式将执行 Personne 到 Integer 的转换;
  • 第 33-39 行:一个静态方法,
    • 接受两个参数:第一个参数类型为 List<Personne>,表示待转换的人员列表;第二个参数类型为 Function<Personne, T>,表示一个函数,该函数基于列表中的每一个人创建一个类型为 T 的对象;
    • 返回类型为 List<T>,其中每个元素均来自 Personne -> T 的转换;
  • 第35-37行:应用Personne -> T的转换。如果方法的第二个参数是对象[mapToName],则将执行Personne -> String的转换。 如果是对象 [mapToAge],则将执行 Personne -> 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<Personne> personnes = Personnes.get();
        // 匿名实现
        Consumer<Personne> consumerAge = new Consumer<Personne>() {
            @Override
            public void accept(Personne personne) {
                System.out.printf(" age de %s = %s%n", personne.getNom(), personne.getAge());
            }
        };
        // lambda 实现
        Consumer<Personne> consumerPoids = p -> System.out.printf(" poids de %s = %s%n", p.getNom(), p.getPoids());        
        // 显示
        for (Personne p : personnes) {
            consumerAge.accept(p);
        }
        System.out.println("--------");
        for (Personne p : personnes) {
            consumerPoids.accept(p);
        }
    }
}
  • 第 14-19 行:接口 [Consumer<Personne>] 由一个匿名类实现,该类的 [accept] 方法用于显示该人的姓名和年龄;
  • 第 21 行:接口 [Consumer<Personne>] 由一个 lambda 函数实现,其隐含方法 [accept] 显示该人的姓名和体重;
  • 第23-25行:人员列表被[consumerAge]的实现所调用;
  • 第 27-29 行:人员列表被 [consumerPoids] 实现所调用;

结果如下:

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<Personne> personnes = Personnes.get();
    // 匿名实现
    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 实现
    BiConsumer<Personne, Integer> biconsumerPoids = (p, i) -> {
      p.setPoids(p.getPoids() + i);
      System.out.printf("poids de %s = %s%n", p.getNom(), p.getPoids());
    };
    // 显示
    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 行:通过 [biconsumerPoids] 实现处理人员列表;

所得结果如下:

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<Personne> personnes = Personnes.get();
        // 匿名实现
        BiFunction<Personne, Integer, Integer> biFunctionAge = new BiFunction<Personne, Integer, Integer>() {
            @Override
            public Integer apply(Personne personne, Integer integer) {
                return personne.getAge() + integer;
            }
        };
        // lambda 实现
        BiFunction<Personne, Integer, Double> biFunctionPoids = (p, i) -> {
            return p.getPoids() + i;
        };
        // 显示
        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<Personne, Integer, Integer> 通过一个匿名类实现。该类中的方法 [apply] 返回第一个参数中传入的人的年龄加上第二个参数的值;
  • 第 21-23 行:接口 BiFunction<Personne, Integer, Double> 通过一个 lambda 函数实现。方法 [apply] 返回第一个参数中传入的人的体重加上第二个参数的值;
  • 第 25-27 行:将 [biFunctionAge] 实现应用于人员;
  • 第29-31行:将实现 [biFunctionPoids] 应用于人员;

所得结果如下:

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) {
        // 匿名实现
        Supplier<Personne> supplier = new Supplier<Personne>() {
            // 人员列表
            List<Personne> personnes = Personnes.get();

            // 接口实现
            @Override
            public Personne get() {
                int i = new Random().nextInt(personnes.size());
                return personnes.get(i);
            }
        };
        // 数据分析
        for (int i = 0; i < 5; i++) {
            affiche(supplier);
        }
    }

    // 人员显示
    public static void affiche(Supplier<Personne> supplier) {
        System.out.println(supplier.get());
    }
}
  • 第 13-28 行:实现类型 Supplier<Personne>
  • 第 31-33 行:静态方法 [affiche] 期望一个类型为 Supplier<Personne> 的参数;
  • 第25-27行:调用实例Supplier<Personne>

得到以下结果:

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"}