Java8函数式编程学习笔记(初探)

编程语言的整个目的就在于操作值,要是按照历史上编程语言的传统,这些值被成为一等值,而编程语言中的其他结构也许有助于表示值的结构,但在程序执行期间不能传递,因此为二等值,比如方法和类等则是二等值,类可以实例化来产生值,但方法和类本身都不是值,java8的设计者则决定允许方法作为值,让编程更轻松.

举个栗子

就农场库存程序而言,如果要实现一个从列表中塞选出绿色苹果的功能,可能会这样写(1.0)

1
2
3
4
5
6
7
8
9
public static List<Apple> filterGreenApples(List<Apple> inventory) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if ("green".equals(apple.getColor())){
result.add(apple);
}
}
return result;
}

假如这个时候要赛选出不仅是绿色的苹果,可能会加入一个颜色作为参数,就象这样(2.0)

1
2
3
4
5
6
7
8
9
public static List<Apple> filterGreenApples(List<Apple> inventory, String color) {
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory) {
if (color.equals(apple.getColor())){
result.add(apple);
}
}
return result;
}

但这个时候别人可能还想区分出重苹果和轻苹果,你可能会这样来赛选重苹果

1
2
3
4
5
6
7
8
9
public static List<Apple> filterApplesByWeight(List<Apple> inventory, int weight){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if (apple.getWeight() > weight) {
result.add(apple);
}
}
return result;
}

这样解决确实不错,但是却复制了大量的代码来实现功能,它打破了DRY(Don’t Repeat Yourself)的软件工程原则,会许这时候你可能会将两种条件结合起来,并用一个标识表示赛选那种类型的苹果,像这样(3.0)

1
2
3
4
5
6
7
8
9
public static List<Apple> filterGreenApples(List<Apple> inventory, String color, Integer weight, boolean flag){
List<Apple> result = new ArrayList<>();
for (Apple apple : inventory){
if ((flag && apple.getColor().equals(color)) || (!flag && apple.getWeight() > weight)) {
result.add(apple);
}
}
return result;
}

但是,这并不是一个可行的解决方案,首先别人阅读你的代码的时候,true|false表示什么?如果这个时候还有更加复杂的塞选情况又该怎么办?java8的行为化参数可以解决这个问题

行为参数化

让方法接受多种行为(或战略)作为参数,并在内部使用,来完成不同的行为.

好处: 多种行为,一个参数,可以把迭代要塞选的集合的逻辑和对集合中每个元素应用的行为区分开来,这样可以重复使用同一个方法,给他不同的行为达到不同的目的.

定义一个接口来对选择标准建模

1
2
3
public interface ApplePredicate {
boolean test (Apple apple);
}

现在就可以为它进行多个实现了,比如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class AppleGreenColorPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
//仅选出绿色的苹果
return "green".equals(apple.getColor());
}
}
public class AppleHeavyWeightPredicate implements ApplePredicate {
@Override
public boolean test(Apple apple) {
//仅选出重的苹果
return apple.getWeight() > 150;
}
}

现在经过java8的改良,代码可能会是这个样子(4.0)

1
2
3
4
5
6
7
8
9
public static List<Apple> filterApples(List<Apple> apples, ApplePredicate predicate){
List<Apple> result = new ArrayList<>();
for (Apple apple : apples){
if (predicate.test(apple)){
result.add(apple);
}
}
return result;
}

每次调用的时候,只需要传递相应的行为即可,比如

1
List<Apple> greenApples = filterApples(inventory,new AppleGreenColorPredicate());

这样一来,每次塞选的是什么苹果完全取决于通过ApplePredicate对象传递的代码,换句话说,把filterApple的方法行为参数化了

当然,到了这里你可能还觉得不甘心,因为每次都要去创建一个新的类,有点啰嗦,所以,你可以使用匿名类来完成这一工作(5.0)

1
2
3
4
5
6
List<Apple> redApples = filterApples(inventory, new ApplePredicate() {
@Override
public boolean test(Apple apple) {
return "red".equals(apple.getColor());
}
});

但是,匿名类还是不够还,因为它占用了很多空间,所以,在java8中可以直接使用Lambda表达式来完成(6.0)

1
List<Apple> weightApples = filterApples(inventory, (Apple apple) -> apple.getWeight() > 150);

到目前为止,总算是可以解决啰嗦麻烦的问题了,因为它更像在描述问题本身.现在你可以将List抽象画,让他应用面更加广泛

1
2
3
4
5
6
7
8
9
10
11
12
13
public interface Predicate<T>{
boolean test(T t);
}
public static <T> List<T> filter(List<T> list, Predicate<T> p){
List<T> result = new ArrayList<>();
for(T e : list){
if(p.test(e)){
result.add(e);
}
}
return result;
}

现在在灵活性和简洁性之间找到了平衡点,这是java8带来的便利.

文章目录
  1. 1. 举个栗子
    1. 1.1. 行为参数化
|