[译]JavaSE 8 :Lambda 快速学习(四) 完结

Lambda Expressions and Collections

前面的章节已经介绍过Function接口,也已经实现基本的Lambda表达式语法示例.这一章节我们重新回顾Lambda表达式如何通过Collection类来提升.

Lambda Expressions and Collections

到目前所创建的示例中,集合只用到一点.但是一些Lambda表达式新特征改变了集合以往的使用方式.这一章节将介绍这些新特征.

新增类

Drivers,pilotsdraftees查询条件都已经被封装到SearchCriteria
类中
.

SearchCriteria.java

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();
   }

   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;

   }

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

Predicate依赖的查询条件存储在这个类中,对于我们的测试方法也是可以获取到的.

循环

第一个新特性就是对于每个集合类都有一个forEach新方法.下面是打印Person列表的一些示例.

Test01ForEach.java

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方法打印出在列表中的每个人。第二个例子演示的是方法引用,在方法已经存在且表示类执行的操作的情况下使用,这种语法可以代替正常的Lambda语法。最后的一个示例展示了printCustom方法在这种情况下也可以使用。请注意当Lambda表达式中包含另一个表达式时的变量名轻微变化。

你可以用这种方式对任何集合进行迭代。基本的结构与增强的for循环相似,但是在类内包含迭代机制带来了许多好处。

链和过滤器

除了循环遍历一个集合的内容,你还可以将这些方法链在一起.第一个方法可以看做是一个过滤器把Predicate接口作为参数.

下面的示例是遍历一个List经过过滤之后的结果集.

Test02Filter.java

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);

   }
 }

第一个和最后一个循环演示了List是如何通过查询条件来过滤的.下面是最后一个循环的输出:

=== Eastern Draftee Phone List ===

Name: Baker Bob
Age: 21 Gender: MALE
EMail: bob.baker@example.com
Phone: 201-121-4678
Address: 44 4th St, Smallville, KS 12333

Name: Doe John
Age: 25 Gender: MALE
EMail: john.doe@example.com
Phone: 202-123-4678
Address: 33 3rd St, Smallville, KS 12333

越来越懒

这些特征都很有用,但为何在已经有了for循环的前提下还要把这些特性加入到集合类呢?通过把迭代功能集合到一个库中,它可以让java开发者能做到更好的代码优化.为了更好解释,我们需要了解下面的术语定义:

  • Laziness(惰性):在编程中,惰性是指只处理那些你想要处理的并且你需要处理的对象.在前面的例子中,最后的循环遍历是惰性的因为它只遍历了List过滤后剩余的两个Person对象.这样代码更高效因为最终的处理步骤只发生在被选择的对象上.

  • Eagerness(急切化):List的每个对象上都执行操作的代码被认为是"急切的".例如,一个增强的for循环遍历整个List只为处理两个对象,这样可以认为是更"急切的"途径.

通过把循环集成到Collections库中,当有机会的时候代码可以更好的优化成"惰性"操作."急切的"做法更具有意义的时候(例如计算求和或者平均值),"急切的"操作仍可以使用.这样更高效更灵活相对与总使用"急切"操作而言.

 

stream方法

在前面的代码示例中,我们注意到stream方法在过滤之前和循环开始的时候被调用.这个方法将一个Collection作为输入并返回一个java.util.stream.Stream接口作为输出.一个Stream代表一个元素集合,任何方法都可以链上(个人注解:类似于建造者模式的一种写法,你可以连续调用stream的各种方法)。在默认情况下,一旦元素被消耗它们将不能从stream再次获取到(个人注解:例如调用forEach方法)。因此,一个操作链只能在一个特定的Stream上执行一次。此外,Stream可以串行(默认情况下)或者并行这依赖于方法的调用。在这个章节最后有一个并行处理的Stream示例。

变化和结果

前面提到,一个Stream在使用之后被丢弃。因此,在集合里的元素是不能使用Stream来交换或者改变。但是如果你想保存这些经过链操作之后的元素该怎么办呢?你可以将它们保存到另一个新的集合中。下面的代码将展示如何做到。

Test03toList.java

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中,并遍历这个List

使用map计算

map方法一般和filter使用.该方法从一个类中获取某一属性并使用它.下面的例子演示如何使用年龄来计算的.

Test04Map.java

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(p -> p.getAge())
             .sum();

     // Get average of ages
     OptionalDouble averageAge = pl
             .parallelStream()
             .filter(search.getCriteria("allPilots"))
             .mapToDouble(p -> p.getAge())
             .average();

     System.out.println("Total Ages: " + totalAge);
     System.out.println("Average Age: " + averageAge.getAsDouble());

   }

 }

这个类的输出如下:

== Calc Old Style ==
Total Ages: 150
Average Age: 37

== Calc New Style ==
Total Ages: 150
Average Age: 37.5

这个程序计算在列表中所有飞行员的平均年龄.第一个循环演示了以前的方式通过for循环来计算.第二个循环使用map方法从串行流中获取每个人的年龄.注意totalAge是个long型。map方法返回一个IntStream对象,它包含sum方法返回long型。

注意:为了第二次计算平均值,不需要再计算年龄的总和。这只是为了教学演示如何使用sum方法。

最后的循环从流(Stream)中计算平均年龄。注意parallelStream方法是用来获取一个并行流这样就可以并行计算数值了。当然返回值跟上面的例子也有点不同。

资源

点击打开链接

 

总结

     在这个教程中,你已经学习了如何使用:

  • Java中的匿名内部类

  • 在Java SE 8中Lambda表达式代替匿名内部类

  • Lambda表达式的正确语法
  • 使用Predicate接口在列表中进行搜索
  • Function接口来处理对象和处理类型不一致的对象

  • 在Java SE 8中Collections加入的新特征来支持Lambda表达式

    <div>
        作者:qarkly112649 发表于2014/3/6 21:39:48 [原文链接](http://blog.csdn.net/qarkly112649/article/details/20654197)
    </div>
    <div>
    阅读:376 评论:0 [查看评论](http://blog.csdn.net/qarkly112649/article/details/20654197#comments)
    </div>