本章节将介绍函数组合和集合管道,您可以结合这两种模式来迭代代码中的集合。了解这些模式的结构有助于您搭建自己的 java 程序,从而充分利用高阶函数和拉姆表达式。
语句与表达式我们在代码中查找 for 循环,回惊奇的发现您的代码中对 for 循环的使用非常频繁。我们将这种情形称为 for 重复:只要我们需要重复似乎就会用到 for。
在 Java 中 for 和 while 都是语句。语句执行一个操作但是不会生成结果。就本质而言,任何执行有用的操作语句都会导致数据变化。这是语句表达其效果的唯一方式。而表达式相反:它们可以得出结果而不会导致变化。在代码中使用语句就像是团队成员合作处理一部分工作,但是无法在团队之间交接工作结果。分享结果的唯一方法是将它放在桌子上或架子上,让另一位团队成员可以获得它。表达式的工作更像是一条链条:当某个人完成一项任务时,他将结果转交给链条的下一个人。
表达式帮助我们实现集合管道模式,也可以称之为运算序列,会将从一次运算收集的输出提供给下一次运算。集合管道模式尽管在面向对象编程中得到了使用,但是它在函数编程中更常见。
函数组合和集合管道模式是两种可协同工作的模式。下面我们先用 for 语句解决一个问题。然后将介绍如何使用这两种模式更高效地解决同一个问题。
使用语句进行迭代和排序首先创建一个 Car 类:
public class Car {
private String make;
private String model;
private int year;
protected Car(String make, String model, int year) {
super();
this.make = make;
this.model = model;
this.year = year;
}
public String getMake() {
return make;
}
public String getModel() {
return model;
}
public int getYear() {
return year;
}
}
下面我们添加一个 Car 的实例集合:
public class Iterating {
public static List<Car> createCars() {
return Arrays.asList(new Car("Jeep", "Wrangler",2011),new Car("长城","越野",2017)
,new Car("比亚迪E6","电动",2016),new Car("特斯拉", "modles", 2015)
,new Car("Jeep", "Comanche", 1990));
}
}
我们使用命令编程的方式来迭代这个列表,获取 2000 年后制造的汽车名称,然后按照年份进行升序排序:
public class GetCar {
public static List<String> getModelsAfter2000For(List<Car> cars) {
//找出2000年后生产的车
List<Car> cars2 = new ArrayList<>();
for (Car car : cars) {
if (car.getYear() >2000) {
cars2.add(car);
}
}
//排序
Collections.sort(cars2, new Comparator<Car>() {
@Override
public int compare(Car o1, Car o2) {
// TODO Auto-generated method stub
return new Integer(o1.getYear()).compareTo(o2.getYear());
}
});
List<String> models = new ArrayList<>();
for (Car car : cars2) {
models.add(car.getModel());
}
return models;
}
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(getModelsAfter2000For(Iterating.createCars()));
}
}
测试结果:
[Wrangler, modles, 电动, 越野]
我们在上面的例子中使用了三个循环,第一个循环我们接收一个汽车的列表作为参数提取出 2000 后制造的汽车,然后将它放入下一个列表中。接下来这个列表按照年份进行排序。最后,我们在循环处理列表已得到列表中的型号名称。
这个演示程序向我们展示了语句的效果。尽管函数和方法可用作表达式,但是 Collectionssort 方法并没有返回结果。因为它被用作语句,所以也改变了参数列表的内容。两个 for 循环在迭代的过程中都改变了相应的列表。因此需要很多的垃圾变量来接收和处理。
使用集合管道进行迭代和排序函数式编程中,通常会通过一系列更小的模块化函数或运算来对复杂运算进行排序。这个系列的运算被称之为函数组合。当一个数据集合流经一个函数组合时,它就变成一个集合管道。函数组合和集合管道是函数式编程中最常用的两种设计模式。无需很多的 for 循环,依据问题我们能使用多种工具。无需像命令式编程哪样对所有的运算都使用语句,函数式编程鼓励使用表达式。表达式没有改变对象的副作用。而是返回给我们一个结果。我们可以将它传递给另一个函数,我们通过这种方式创建集合管道。
public static List<String> getModelsAfter2000UsingFor(List<Car> cars) {
return
cars.stream()
.filter(car -> car.getYear() > 2000)
.sorted(Comparator.comparing(Car::getYear))
.map(Car::getModel)
.collect(Collectors.toList());
}
执行结果:
[Wrangler, modles, 电动, 越野]
两个方法得到的结果是一样的。但是要注意代码中的不同之处:
- 函数式代码比命令式代码更简洁
- 函数式代码不会表现出明显的易变性,而且使用更少的垃圾变量
- 第二个方法中使用的函数都是有返回值的表达式。
- 最后一个方法使用了集合管道模式,而且非常富余表达
短短的几行代码,我们的意图就表现的很明显:给定一个汽车的集合,过滤和提取仅在 2000 年后制造的汽车;然后按照年份排序,将这些对象映射转换为他们的型号,最后将结果收集到一个列表中。这个例子中的代码丰富且富余表达,部分原因是使用了方法引用。将一个拉姆表达式传递给 filter 很有用。因为它可以获取给定对象的 year 属性将其与 year 2000 进行比较。传递给 Map 方法的表达式 car -> car.getModel(),该表达式仅返回给定对象的某个属性,不执行任何实际计算或运算。最好把它替换为一个方法引用。我们将方法引用 Car::getModel 传递给 map 方法,而不传递拉姆达表达式。类似地,我们将方法引用 Car::getYear 传递给 comparing 方法,而不传递拉姆达表达式 car -> car.getYear()。方法引用简短、简洁且富于表达。最好尽可能地使用它们。
总结在命令式编程中,对于大部分的数据处理,通常都会使用 for 和 while 循环。在 Java 8 的版本中函数式编程是一个非常流行的替代方法。函数组合是一项简单技术,有助于对模块化函数进行排序,从而创建更复杂的运算。按照这个顺序处理数据时您有一个集合管道。结合使用函数组合和集合管道模式,可以创建复杂的程序,让数据从上游到下游,经历一些列的转换。
热门评论
老师,谢谢您无私的奉献首先,但是我有个小问题,管道这玩意从第n个过滤器出来的数据,是不是发给第n+1个过滤器后数据就没了啊,您这个如果把每个函数看作一个过滤器吧,他们之间传送的是实例化对象吧,传送实例化对象那数据是怎么个结果啊?