Java中函数式编程的谓词函数(Predicates)

来源:互联网 发布:访客网络还是 编辑:程序博客网 时间:2024/05/21 10:29

什么是谓词函数?

Apache Commons Collections里的谓词函数仅仅只是一个只有一个方法的接口:

evaluate(Object object): boolean

这就是谓词函数,输入一个对象,返回true或false。在Google Guava中,定义了Predicate接口,该接口包含一个带有泛型参数的方法:

apply(T input): boolean

如果想在程序中使用谓词函数,只需要利用自己的逻辑实现该接口即可。

例子:

假设有一个订单列表,每个订单用PurchaseOrder表示,PurchaseOrder中包含日期、顾客和状态。不同的用例要求你有不同的输出,比如获取某个顾客所有等待发货、已发货、已交付或者过去一个小时内完成的订单。当然可以在循环中使用if判断实现这些功能:

//List<PurchaseOrder> orders...public List<PurchaseOrder> listOrdersByCustomer(Customer customer) {    final List<PurchaseOrder> selection = new ArrayList<PurchaseOrder>();    for (PurchaseOrder order : orders) {        if (order.getCustomer().equals(customer)) {            selection.add(order);        }    }    return selection;}
以上是获取某个客户所有的订单的代码。不同的功能需要编写多个类似的循环:

public List<PurchaseOrder> listRecentOrders(Date fromDate) {        final List<PurchaseOrder> selection = new ArrayList<PurchaseOrder>();        for (PurchaseOrder order : orders) {            if (order.getDate().after(fromDate)) {                selection.add(order);            }        }        return selection;}
这些重复的代码非常明显:除了if的判断条件之外没有任何差异。采用谓词函数的思想在于,利用传入到函数内的谓词的调用替代if语句块里的硬编码的判断条件。这就意味着,你只需要编写带有谓词函数作为参数的方法,就可覆盖所有的甚至你还不知道的测试用例:

public List<PurchaseOrder> listOrders(Predicate<PurchaseOrder> condition) {    final List<PurchaseOrder> selection = new ArrayList<PurchaseOrder>();    for (PurchaseOrder order : orders) {        if (condition.apply(order)) {            selection.add(order);        }    }    return selection;}
如果需要考虑到复用,则可以将谓词函数声明成一个单独的类,否则可以把谓词声明成匿名类:

final Customer customer = new Customer("BruceWaineCorp");final Predicate<PurchaseOrder> condition = new Predicate<PurchaseOrder>() {    public boolean apply(PurchaseOrder order) {        return order.getCustomer().equals(customer);    }};
Apache或Google的API提供了一个类似java.util.Collections的命名为Collections2的类,它提供了与我们先前编写的代码功能类似的filter()函数,所以可以将方法重构成无循环的版本:

public Collection<PurchaseOrder> selectOrders(Predicate<PurchaseOrder> condition) {    return Collections2.filter(orders, condition);}
在一个类似的场景中,我们可以要求返回在给定的迭代器之上过滤好的只符合谓词函数的元素的迭代器(装饰模式)。

Iterator filteredIterator = Iterators.filter(unfilteredIterator, condition);
Iterable接口和循环使用起来非常方便:

public Iterable<PurchaseOrder> selectOrders(Predicate<PurchaseOrder> condition) {    return Iterables.filter(orders, condition);}// you can directly use it in a foreach loop, and it reads well:for (PurchaseOrder order : orders.selectOrders(condition)) {    //...}


为了使用谓词,我们必须声明自己的谓词接口,或者为应用程序中使用到的谓词参数都声明一个类。然而从类似Guava以及Commons的API中使用标准谓词接口的好处是:你可以结合这类API提供的大量优秀组件实现你自己的谓词函数。
如果你需要的是判断一个对象是否为空或者不为空的条件,你不需要自己实现一个谓词函数,只需要使用现成的谓词就可以了:

// gives you a predicate that checks if an integer is zeroPredicate<Integer> isZero = Predicates.equalTo(0);// gives a predicate that checks for non null objectsPredicate<String> isNotNull = Predicates.notNull();// gives a predicate that checks for objects that are instanceof the given ClassPredicate<Object> isString = Predicates.instanceOf(String.class);
对于给定的谓词,你可以反转它(返回相反的返回值,比如true变成false):

Predicates.not(predicate);
利用AND、OR操作结合多个谓词:

Predicates.and(predicate1, predicate2);Predicates.or(predicate1, predicate2);// gives you a predicate that checks for either zero or nullPredicate<Integer> isNullOrZero = Predicates.or(isZero, Predicates.isNull());