Java8新特性入门一 (行为参数化)

通过行为参数化传递代码

应对不断变化的需求

有实体如下:
Apple.java

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Apple {

    private String name;

    private String color;

    private int mass;
}

有集合如下:

List<Apple> inventory = Arrays.asList(new Apple("aaa","green",80),
                new Apple("bbb","green",155),
                new Apple("ccc","red",120));

1. 筛选绿苹果

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. 把颜色作为参数把颜色作为参数

public static List<Apple> filterRedApples(List<Apple> inventory) {
        List<Apple> result = new ArrayList<>();
        for(Apple apple: inventory){
            if("red".equals(apple.getColor())){
                result.add(apple);
            }
        }
        return result;
}

调用方法

List<Apple> greenApples = filterApplesByColor(inventory, "green");
List<Apple> redApples = filterApplesByColor(inventory, "red");

这样就得到结果了…

改变需求: 要是能区分轻的苹果和重的苹果就太好了。重的苹果一般是重量大于150克。
实现代码如下:

//区分轻的苹果和重的苹果就太好了。重的苹果一般是重量大于150克
public static List<Apple> filterApplesByWeight(List<Apple> inventory,int weight) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(apple.getMass() > weight){
            result.add(apple);
        }
    }
    return result;
}

缺点:复制了大部分的代码来实现遍历库存,并对每个苹果应用筛选条件。

3. 对你能想到的每个属性做筛选
一种把所有属性结合起来的笨拙尝试如下:

public static List<Apple> filterApplesByWeight(List<Apple> inventory,String color,int weight,boolean flag) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(flag && apple.getColor().equals(color) || (!flag && apple.getMass()>weight)){
            result.add(apple);
        }
    }
    return result;
}

你可以这么用(但真的很笨拙):
调用如下:

List<Apple> greenApples = filterApplesByWeight(new ArrayList(),"red",0,true);
List<Apple> heavyApples = filterApples(inventory, "", 150, false);

问题:

/**
 * 客户端代码糟糕透顶,
 * 1.  true 和 false 是什么意思
 * 2. 这个解决方案还是不能很好地应对变化的需求
 * 3. 你对苹果的不同属性做筛选,比如大小、形状、产地等,又怎么办?
 * 4. 要求你组合属性,做更复杂的查询,比如绿色的重苹果,又该怎么办?
 *
 * 解决之道:如何利用行为参数化实现这种灵活性
 */

行为参数化

已经看到了,你需要一种比添加很多参数更好的方法来应对变化的需求。让
我们后退一步来看看更高层次的抽象。一种可能的解决方案是对你的选择标准建模:你考虑的是苹果,需要根据 Apple 的某些属性(比如它是绿色的吗?重量超过150克吗?)来返回一个boolean 值。我们把它称为谓词(即一个返回 boolean 值的函数)。让我们定义一个接口来对选择标准建模

/**
 * 定义一个接口,定义选择标准
 * 这里定义实现,定义一族算法,把他们封装起来称为策略模式
 *
 * 算法族就是 ApplePredicate
 *
 * 不同的策略就是:AppleHeavyWeightPredicate和 AppleGreenColorPredicate
 *
 *
 * 但是,该怎么利用 ApplePredicate 的不同实现呢?你需要 filterApples 方法接受
 * ApplePredicate 对象,对 Apple 做条件测试。这就是行为参数化:让方法接受多种行为(或战
 * 略)作为参数,并在内部使用,来完成不同的行为。
 *
 * 要在我们的例子中实现这一点,你要给 filterApples 方法添加一个参数,让它接受
 * ApplePredicate 对象。这在软件工程上有很大好处:现在你把 filterApples 方法迭代集合的
 * 逻辑与你要应用到集合中每个元素的行为(这里是一个谓词)区分开了。
 */
public interface ApplePredicate {

    /**
     * 谓词(即一个返回 boolean 值的函数)
     * @param apple
     * @return
     */
    boolean test(Apple apple);
}

现在你就可以用 ApplePredicate 的多个实现代表不同的选择标准了
仅仅选出重的苹果

/**
 * 仅仅选出重的苹果
 */
public class AppleHeavyWeightPredicate implements ApplePredicate{

    @Override
    public boolean test(Apple apple) {
        return apple.getMass() > 150;
    }
}

仅仅选出绿苹果

/**
 * 仅仅选出绿苹果
 */
public class AppleGreenColorPredicate implements ApplePredicate{
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
}

UML类图

根据抽象条件筛选
这里值得停下来小小地庆祝一下。这段代码比我们第一次尝试的时候灵活了,读起来、用起来也更容易!现在你可以创建不同的 ApplePredicate 对象,并将它们传递给 filterApples方法。filterApples 方法的行为取决于你通过 ApplePredicate 对象传递的代码。换句话说,你把 filterApples 方法的行为参数化了!

public static List<Apple> filterApples(List<Apple> inventory,ApplePredicate predicate) {
    List<Apple> result = new ArrayList<>();
    for(Apple apple: inventory){
        if(predicate.test(apple)){//predicate 封装了测试苹果的条件
            result.add(apple);
        }
    }
    return result;
}

筛选出绿色苹果

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

筛选出重苹果

List<Apple> heavyApples = filterApples(new ArrayList(),new AppleHeavyWeightPredicate());

问题:每多一个刷选,就需要额外创建一个类,实现ApplePredicate,啰嗦。

解决知道:匿名内部类

List<Apple> apples = filterApples(inventory, new ApplePredicate() {
    @Override
    public boolean test(Apple apple) {
        return "green".equals(apple.getColor());
    }
});

缺点:

  1. 它往往很笨重,因为它占用了很多空间
  2. 很多程序员觉得它用起来很让人费解
    解决知道:使用 Lambda 表达式
List<Apple> apples2 = filterApples(inventory,(Apple apple) -> "green".equals(apple.getColor()));

不得不承认这代码看上去比先前干净很多。这很好,因为它看起来更像问题陈述本身了。我们现在已经解决了 啰 嗦的问题。下图对我们到目前为止的工作做了一个小结
在这里插入图片描述

进一步加强:
filterApples 方法还只适用于 Apple 。你还可以将 List 类型抽象化,从而超越你眼前要处理的问题,改进如下:

public interface Predicate<T> {
    boolean test(T t);
}

刷选方法如下:

public static <T> List<T> filter(List<T> inventory,Predicate<T> predicate) {
    List<T> result = new ArrayList<>();
    for(T t: inventory){
        if(predicate.test(t)){//predicate 封装了测试苹果的条件
            result.add(t);
        }
    }
    return result;
}

现在你可以把 filter 方法用在香蕉、桔子、 Integer 或是 String 的列表上了
代码如下:

List<Apple> apples2 = filterApples(inventory,(Apple apple) -> "red".equals(apple.getColor()));

List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
List<Integer> evenNumbers = filter(numbers,(Integer i) -> i%2 == 0);

酷不酷?你现在在灵活性和简洁性之间找到了最佳平衡点,这在Java 8之前是不可能做到的!

真实的例子

  • 使用Comparator排序
apples2.sort((Apple o1, Apple o2)-> o1.getColor().compareTo(o2.getColor()));
//可以替换如下
apples2.sort(Comparator.comparing(Apple::getColor));
  • 用 Runnable 执行代码块
Thread t = new Thread(() -> System.out.println("Hello world"));

猜你喜欢

转载自blog.csdn.net/guo20082200/article/details/82913744