Java SE 8: Lambda Quick Start Part.IV (Fin)

狂风中的少年 提交于 2019-12-20 18:02:51

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

前面的章节中介绍了函数式接口并完成了一个基本的lambda表达式语法示例. 本节回顾lambda表达式如何改善集合类.

Lambda表达式和集合类

在前面的例子中, 集合类被多次用到. 然而, 若干新的lambda表达式特性改变了它们的使用方法. 本节讲介绍一部分这样的新特性.

附加类

司机, 飞行员, 役男的搜索条件被封装到SearchCriteria类中

package com.example.lambda;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Predicate;

/**
 * @author MikeW
 */
public class SearchCriteria {

    private final Map<String, Predicate<Person>> searchMap = new HashMap<>();

    private SearchCriteria() {
        super();
        initSearchMap();
    }

    public static SearchCriteria getInstance() {
        return new SearchCriteria();
    }

    private void initSearchMap() {
        Predicate<Person> allDrivers  = p -> p.getAge() >= 16;
        Predicate<Person> allDraftees = p -> p.getAge() >= 18 && p.getAge() <= 25 && p.getGender() == Gender.MALE;
        Predicate<Person> allPilots   = p -> p.getAge() >= 23 && p.getAge() <= 65;
        searchMap.put("allDrivers", allDrivers);
        searchMap.put("allDraftees", allDraftees);
        searchMap.put("allPilots", allPilots);
    }

    public Predicate<Person> getCriteria(String PredicateName) {
        Predicate<Person> target;
        target = searchMap.get(PredicateName);
        if (target == null) {
            System.out.println("Search Criteria not found... ");
            System.exit(1);
        }
        return target;
    }
}

基于Predicate的搜索条件存储在此类中, 并且可用于我们的测试方法.

循环

第一个要注意的特性是任何集合类都可以使用的新的forEach方法. 以下是打印Person列表的几个示例.

package com.example.lambda;

import java.util.List;

/**
 * @author MikeW
 */
public class Test01ForEach {

    public static void main(String[] args) {
        List<Person> pl = Person.createShortList();
        System.out.println("\n=== Western Phone List ===");
        pl.forEach(p -> p.printWesternName());
        System.out.println("\n=== Eastern Phone List ===");
        pl.forEach(Person::printEasternName);
        System.out.println("\n=== Custom Phone List ===");
        pl.forEach(p -> {
            System.out.println(p.printCustom(r -> "Name: " + r.getGivenName() + " EMail: " + r.getEmail()));
        });
    }
}

第一个例子展示了使用标准lambda语法调用printWesternName方法来打印列表中的每个Person对象. 第二个例子演示了方法引用. 在已经存在对该类 (Person) 执行操作的方法 (printEasternName) 的情况下, 可以使用该语法而不是正常的lambda表达式语法. 最后一个例子显示了在这种情况下使用printCustom方法运行. 请注意, 当一个lambda表达式包含在另一个lambda表达式中时,变量名称稍有不同.

所有的集合类都能用此方法迭代. 基本结构类似于增强型for循环. 但是. 在类中包括一个迭代机制提供了许多好处.

链接和过滤器

除了循环集合的内容之外,您还可以将方法链接在一起。第一种方法是将Predicate接口作为参数的过滤器。以下示例在首次过滤结果后循环列表。

package com.example.lambda;

import java.util.List;

/**
 * @author MikeW
 */
public class Test02Filter {
    public static void main(String[] args) {
        List<Person> pl = Person.createShortList();
        SearchCriteria search = SearchCriteria.getInstance();

        System.out.println("\n=== Western Pilot Phone List ===");
        pl.stream().filter(search.getCriteria("allPilots")).forEach(Person::printWesternName);

        System.out.println("\n=== Eastern Draftee Phone List ===");
        pl.stream().filter(search.getCriteria("allDraftees")).forEach(Person::printEasternName);
    }
}

这两个循环演示了如何基于搜索条件筛选列表.

Getting Lazy

这些特性很有用, 但是为什么当已经有非常完美的for循环了还把它们加入到集合类中呢? 通过将迭代特性移动到库中,它允许Java的开发人员进行更多的代码优化。为了进一步说明,几个术语需要定义。

  • Laziness: 在编程中,懒汉模式指的是只处理需要处理的对象。在上面的例子中, 后一个循环就是"饿汉模式", 因为在列表被过滤后, 它只遍历了两个Person对象. 代码会更加高效, 因为最终的处理步骤只处理被选出的对象.
  • Eagerness: 对列表中的每个对象执行操作的代码被认为是“饿汉模式”. 例如,一个增强的for循环遍历整个列表以处理两个对象,被认为是一种更“急切”的方法。

通过操作集合的循环, 当机会出现时,代码可以更好地针对“懒汉模式”进行优化. 而当饿汉模式更有意义时(比如, 求和或求平均), 仍然采用饿汉模式. 这种方法比总是使用渴望的操作更有效和灵活。

The stream Method

在前面的代码示例中,请注意,在过滤和循环开始之前调用了stream方法. 此方法使用集合类作为输入,并返回java.util.stream.Stream接口作为输出。一个steam代表了一个其上可以链接各种方法的元素序列. 默认情况下,一旦元素被消费,它们就不再可从stream中获得。 因此, 一个操作链只能在特定的stream上发生一次. 此外, stream可以是串行(默认)或并行的,具体取决于调用的方法。本文的末尾包含有一个并行stream示例.

变更和结果

正如前述, stream在使用后被丢弃. 因此, 集合中的元素不能通过stream被更改或变更. 然而, 如果你想保存操作链从集合中返回的元素呢? 你可以将他们保存到一个新的集合中. 下面的代码将展示如何实现:

public class Test03toList {
  public static void main(String... args){
    List<Person> pl = Person.createShortList();
    SearchCriteria search = SearchCriteria.getInstance();
    // Make a new list after filtering.
    List<Person> pilotList = pl.stream().filter(search.getCriteria("allPilots")).collect(Collectors.toList());
    System.out.println("\n=== Western Pilot Phone List ===");
    pilotList.forEach(Person::printWesternName);
  }
}

collect方法只有一个参数, 就是Collectors类. Collectors类能够基于stream的结果返回一个List或者Set对象. 上面的例子展示了stream的结果是如何在遍历完成时被分配到一个List对象中的.

用映射 (map) 方法计算

映射方法通常和过滤器 (filter) 一起使用. 该方法从一个类接受一个属性, 并做一些事情. 以下示例通过基于年龄执行计算来演示此操作.

public class Test04Map {
  public static void main(String[] args) {
    List<Person> pl = Person.createShortList();
    SearchCriteria search = SearchCriteria.getInstance();
    // Calc average age of pilots old style
    System.out.println("== Calc Old Style ==");
    int sum = 0;
    int count = 0;
    for (Person p:pl){
      if (p.getAge() >= 23 && p.getAge() <= 65 ){
        sum = sum + p.getAge();
        count++;
      }
    }
    long average = sum / count;
    System.out.println("Total Ages: " + sum);
    System.out.println("Average Age: " + average);
    // Get sum of ages
    System.out.println("\n== Calc New Style ==");
    long totalAge = pl.stream().filter(search.getCriteria("allPilots")).mapToInt(Person::getAge).sum();
    // Get average of ages
    OptionalDouble averageAge = pl.parallelStream().filter(search.getCriteria("allPilots")).mapToDouble(Person::getAge).average();
    System.out.println("Total Ages: " + totalAge);
    System.out.println("Average Age: " + averageAge.getAsDouble());    
  }
}

这个程序计算列表中所有飞行员的平均年龄. 第一个for循环展示了旧式算法. 第二个循环通过映射方法来取得串行流中每个person对象的年龄.
注意: 第二次计算平均值时, 并不需要先求和. 不过, 还是作为一种启发性的展示了sum方法.
最后一个循环从stream中计算出了平均年龄. 注意它使用了parallelStream方法来得到一个并行流 (parallel stream), 从而可以同时计算这些值. 它的返回值也有稍许不同(详见optional api).

源代码: LambdaCollectionExamples.zip

总结

通过本教程, 你学到了如何使用:

  • 匿名内部类
  • 通过Lambda表达式替换匿名内部类
  • Lambda表达式语法
  • 用于在列表上执行搜索的谓词 (Predicate) 接口
  • 用于处理对象并生成不同类型的对象的函数接口
  • 在Java SE 8中的Collections类添加的支持lambda表达式的新功能

资源

有关Java SE 8和lambda表达式的更多信息,请参见:

致谢

  • Lead Curriculum Developer: Michael Williams
  • QA: Juan Quesada Nunez
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!