前言:本文是基于ORACLE官网的JavaTM Tutorials的Lambda Expressions章节整理而成。

使用匿名类可能导致程序不够简洁,那么JDK8开始,出现了Lambda表达式可以很好地解决这个问题。

接下来我们通过一个案例,一步一步地理解Lambda表达式:

  • 需求:你要创建一个社交网站的应用,该应用有这个功能:管理员能够进行各种类型的操作,比如给满足特定条件的用户发送email消息。

  • 该应用的用户用Person类表示,并且所有成员存储在List<Person>实例中。

    public class Person {
    
        public enum Sex {
            MALE, FEMALE
        }
    
        String name;
        LocalDate birthday;
        Sex gender;
        String emailAddress;
    
        public int getAge() {
            // ...
        }
    
        public void printPerson() {
            // ...
        }
    }
    

Approach 1: Create Methods That Search for Members That Match One Characteristic

最简单的方法是创建几个方法,每个方法返回复合条件的用户。比如下面的printPersonsOlderThan()会打印出大于age的用户。

public static void printPersonsOlderThan(List<Person> roster, int age) {
    for (Person p : roster) {
        if (p.getAge() >= age) {
            p.printPerson();
        }
    }
}

这么做显然太low了。

Approach 2: Create More Generalized Search Methods

第二种方法返回年龄在low和high之间的用户,比第一种好一点,但也很low。

public static void printPersonsWithinAgeRange(
    List<Person> roster, int low, int high) {
    for (Person p : roster) {
        if (low <= p.getAge() && p.getAge() < high) {
            p.printPerson();
        }
    }
}

Approach 3: Specify Search Criteria Code in a Local Class

第三种方法比前两种方法逼格高一点,可以定制CheckPerson的条件,当需求改变时,不用修改printPersons()的结构。但是却引入了其他的代码(接口,自定义实现类)。

//过滤条件的接口
interface CheckPerson {
    boolean test(Person p);
}
//实现过滤条件接口的类,在test()写条件。
class CheckPersonEligibleForSelectiveService implements CheckPerson {
    public boolean test(Person p) {
        return p.gender == Person.Sex.MALE &&
            p.getAge() >= 18 &&
            p.getAge() <= 25;
    }
}

public static void printPersons(
    List<Person> roster, CheckPerson tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

Approach 4: Specify Search Criteria Code in an Anonymous Class

继续改进,在匿名类中确定查询条件。这一步理解起来应该没什么问题。

printPersons(
    roster,
    new CheckPerson() {
        public boolean test(Person p) {
            return p.getGender() == Person.Sex.MALE
                && p.getAge() >= 18
                && p.getAge() <= 25;
        }
    }
);

Approach 5: Specify Search Criteria Code with a Lambda Expression

我们的主角Lambda表达式姗姗来迟。这里介绍一个概念:函数式接口。所谓函数式接口就是只含有一个抽象方法的接口。(一个函数式接口可能会含有一个或多个default methodsstatic methods)。你可以省略方法名字当你实现它时。

printPersons(
    roster,
    (Person p) -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

Approach 6: Use Standard Functional Interfaces with Lambda Expressions

分析到这里,我们还是自己写了个接口。其实Java8替我们考虑了这些,在java.util.function包下为我们提供了一系列Standard Functional Interfaces(标准函数式接口)。比如在本例中,可以用Predicate<T>代替CheckPerson接口。

//可以看到Predicate<T>接口也有返回值为boolean的test方法
interface Predicate<T> {
    boolean test(T t);
}

//新的方法定义
public static void printPersonsWithPredicate(
    List<Person> roster, Predicate<Person> tester) {
    for (Person p : roster) {
        if (tester.test(p)) {
            p.printPerson();
        }
    }
}

//新的执行方式
printPersonsWithPredicate(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25
);

Approach 7: Use Lambda Expressions Throughout Your Application

上面的方法中不只有一处可用Lambda表达式替换。重新思考下:

如果你想对满足查询条件的用户做一些其他的功能,而不是printPerson()时,可以用Lambda表达式实现一个参数为Person,返回值为void的函数式接口。Java也帮我们想好了, Consumer<T> 接口包含了 void accept(T t)方法。

//使用Consumer<T>接口作为方法参数
public static void processPersons(
    List<Person> roster,
    Predicate<Person> tester,
    Consumer<Person> block) {
        for (Person p : roster) {
            if (tester.test(p)) {
                block.accept(p);
            }
        }
}
//新的实现方式
processPersons(
     roster,
     p -> p.getGender() == Person.Sex.MALE
         && p.getAge() >= 18
         && p.getAge() <= 25,
     p -> p.printPerson()
);

如果你想对用户有更多的操作,比如检索他们的联系方式。那么这时,你需要一个有返回值的接口。Java也为我们提供了,他就是Function<T,R>接口,它有 R apply(T t)方法。

//这样我们在block.accept()中处理的对象就是经过tester判别,mapper返回之后的对象。
public static void processPersonsWithFunction(
    List<Person> roster,
    Predicate<Person> tester,
    Function<Person, String> mapper,
    Consumer<String> block) {
    for (Person p : roster) {
        if (tester.test(p)) {
            String data = mapper.apply(p);
            block.accept(data);
        }
    }
}
//新的实现
processPersonsWithFunction(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    //将Person映射成String
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);

Approach 8: Use Generics More Extensively

使用泛型进一步扩展通用性。

//注意方法声明中的<X,Y>
public static <X, Y> void processElements(
    Iterable<X> source,
    Predicate<X> tester,
    Function <X, Y> mapper,
    Consumer<Y> block) {
    for (X p : source) {
        if (tester.test(p)) {
            Y data = mapper.apply(p);
            block.accept(data);
        }
    }
}

//打印符合查询条件的用户的email地址,可以这样调用processElements()
//和Approach7种调用方法一样。
processElements(
    roster,
    p -> p.getGender() == Person.Sex.MALE
        && p.getAge() >= 18
        && p.getAge() <= 25,
    p -> p.getEmailAddress(),
    email -> System.out.println(email)
);