简体   繁体   English

如何将具有动态值的lambda过滤器转换为方法引用

[英]How to convert lambda filters with dynamic values to method references

I have some Java code which filters a list based on some input. 我有一些Java代码根据一些输入过滤列表。 It currently uses a lambda, for example: 它目前使用lambda,例如:

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects
                .stream()
                .filter( compObject -> allowedTags.contains(compObject.getTag()))
                .collect(Collectors.toList());
}

What I want to do is to move the filter logic to another method to make it re-usable and easily unit testable. 我想要做的是将过滤器逻辑移动到另一种方法,使其可重复使用,并且易于单元测试。 So I wanted to use a method reference in place of the lambda passed to the filter method. 所以我想使用方法引用代替传递给filter方法的lambda。 Easy to do if the filter logic is fairly static (ie list of allowed tags is known at compile time) but I can't figure out how to do this with dynamic data in the filter. 如果过滤器逻辑是相当静态的(即在编译时已知允许的标签列表),则很容易做到,但我无法弄清楚如何使用过滤器中的动态数据执行此操作。

What I wanted was some way to use a method reference and then pass the second dynamic param ie 我想要的是一些使用方法引用然后传递第二个动态参数的方法,即

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects
                .stream()
                .filter(this::filterByAllowedTags, allowedTags)
                .collect(Collectors.toList());
}

So is it possible to do what I want or am I possibly approaching this situation incorrectly? 那么我可以做我想做的事情,还是我可能错误地接近这种情况?

I'd suggest passing in a Predicate as a parameter. 我建议传递一个Predicate作为参数。 That way the caller can filter based on any criteria it wants, including allowedTags or whatever: 这样调用者可以根据自己想要的任何条件进行过滤,包括allowedTags或其他:

public List<ComplexObject> retrieveObjectsFilteredBy(Predicate<ComplexObject> pred) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects.stream()
        .filter(pred)
        .collect(Collectors.toList());
}

This would be called like so: 这将被称为如下:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy(cobj -> allowedTags.contains(cobj.getTag()));

But you could go even further, depending on how much refactoring you're willing to do. 但你可以更进一步,取决于你愿意做多少重构。 Instead of "retrieve" returning a List , how about having it return a Stream ? 而不是“检索”返回List ,如何让它返回一个Stream And instead of the retrieve-filter method returning a List , how about having it return a Stream too? 而不是返回List的retrieve-filter方法,如何让它返回Stream呢?

public Stream<ComplexObject> retrieveObjectsFilteredBy2(Predicate<ComplexObject> pred) {
    Stream<ComplexObject> complexObjects = retrieveAllComplexObjects2();
    return complexObjects.filter(pred);
}

And the calling side would look like this: 主叫方看起来像这样:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy2(cobj -> allowedTags.contains(cobj.getTag()))
            .collect(toList());

Now if you look at it carefully, you can see that the retrieve-filter method isn't adding any value at all, so you might just as well inline it into the caller: 现在,如果你仔细看一下,你可以看到retrieve-filter方法根本没有添加任何值,所以你也可以将它内联到调用者中:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(cobj -> allowedTags.contains(cobj.getTag()))
            .collect(toList());

Of course, depending upon what the caller wants to do, it might not want to collect the results into a list; 当然,根据调用者想要做的事情,它可能不希望将结果收集到列表中; it might want to process the results with forEach() , or something else. 它可能希望使用forEach()或其他方法处理结果。

Now you can still factor out the filter into its own method, for testing/debugging, and you can use a method reference: 现在,您仍然可以将过滤器分解为自己的方法,以进行测试/调试,并且可以使用方法引用:

boolean cobjFilter(ComplexObject cobj) {
    List<String> allowedTags = ... ;
    return allowedTags.contains(cobj.getTag());
}

    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(this::cobjFilter)
            .collect(toList());

If you don't want the filter to have the allowed tags built into it, you can change it from being a predicate into a higher-order function that returns a predicate instead: 如果您不希望过滤器内置允许的标记,则可以将其从谓词更改为返回谓词的高阶函数:

Predicate<ComplexObject> cobjFilter(List<String> allowedTags) {
    return cobj -> allowedTags.contains(cobj.getTag());
}

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(cobjFilter(allowedTags))
            .collect(toList());

Which of these variations makes the most sense depends on what your application looks like and what kind of dynamicism you require in filtering. 哪些变体最有意义取决于您的应用程序的外观以及过滤所需的动态性。

How about the following? 以下怎么样? It extracts the predicate in a separate method to make it easily testable, and can be reused easily. 它以单独的方法提取谓词,使其易于测试,并且可以轻松重用。

public Predicate<ComplexObject> tagAllowed(List<String> allowedTags) {
    return (ComplexObject co) -> allowedTags.contains(co.getTag());
}

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags) {
    List<ComplexObject> complexObjects = retrieveAllComplexObjects();
    return complexObjects
                .stream()
                .filter(tagAllowed(allowedTags))
                .collect(Collectors.toList());
}

The problem with the method reference this::filterByAllowedTags is that it has the shape: 方法引用this::filterByAllowedTags是它具有以下形状:

(ComplexObject, List<String>) -> boolean

But it's being passed into filter() which expects a lambda of the shape: 但它被传递到filter() ,它需要一个形状的lambda:

(ComplexObject) -> boolean

In other words, this::filterByAllowedTags can never be a Predicate , but it could be some alternate interface Predicate2 . 换句话说, this::filterByAllowedTags永远不能是Predicate ,但它可能是一些备用接口Predicate2 Then you'd also need an overload of filter which takes a Predicate2 . 然后你还需要一个带有Predicate2的过滤器重载。

Eclipse Collections (formerly GS Collections) has the method select() which behaves just like filter() , and selectWith() which behaves like the overload I just described. Eclipse Collections (以前的GS Collections)有方法select() ,它的行为就像filter() ,而selectWith()行为就像我刚刚描述的重载一样。

Using select() : 使用select()

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags)
{
    // retrieveAllComplexObjects returns a MutableList, possibly FastList
    MutableList<ComplexObject> complexObjects = retrieveAllComplexObjects();
    // select() returns MutableList here which extends List
    return complexObjects.select(compObject -> allowedTags.contains(compObject.getTag()));
}

Using selectWith() : 使用selectWith()

public List<ComplexObject> retrieveObjectsFilteredByTags(List<String> allowedTags)
{
    // retrieveAllComplexObjects returns a MutableList, possibly FastList
    MutableList<ComplexObject> complexObjects = retrieveAllComplexObjects();
    // select() returns MutableList here which extends List
    return complexObjects.selectWith(this::filterByAllowedTags, allowedTags);
}

private boolean filterByAllowedTags(ComplexObject complexObject, List<String> allowedTags)
{
    return allowedTags.contains(complexObject.getTag());
}

Note: I am a committer for Eclipse Collections. 注意:我是Eclipse Collections的提交者。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM