Lambda Expressions and Collections
前面的章节已经介绍过Function接口,也已经实现基本的Lambda表达式语法示例.这一章节我们重新回顾Lambda表达式如何通过Collection类来提升.
Lambda Expressions and Collections
到目前所创建的示例中,集合只用到一点.但是一些Lambda表达式新特征改变了集合以往的使用方式.这一章节将介绍这些新特征.
新增类
Drivers,pilots和draftees查询条件都已经被封装到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表达式