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

bmo*_*ney 5 java lambda java-8 java-stream method-reference

我有一些Java代码根据一些输入过滤列表.它目前使用lambda,例如:

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

我想要做的是将过滤器逻辑移动到另一种方法,使其可重复使用,并且易于单元测试.所以我想使用方法引用代替传递给filter方法的lambda.如果过滤器逻辑是相当静态的(即在编译时已知允许的标签列表),则很容易做到,但我无法弄清楚如何使用过滤器中的动态数据执行此操作.

我想要的是一些使用方法引用然后传递第二个动态参数的方法,即

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

那么我可以做我想做的事情,还是我可能错误地接近这种情况?

Stu*_*rks 4

我建议传入 aPredicate作为参数。这样,调用者可以根据它想要的任何标准进行过滤,包括allowedTags或其他:

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

这将被称为这样:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy(cobj -> allowedTags.contains(cobj.getTag()));
Run Code Online (Sandbox Code Playgroud)

但您可以走得更远,具体取决于您愿意进行多少重构。与其“检索”返回 a List,不如让它返回 a 怎么样Stream?与其让retrieve-filter方法返回a List,不如让它Stream也返回a怎么样?

public Stream<ComplexObject> retrieveObjectsFilteredBy2(Predicate<ComplexObject> pred) {
    Stream<ComplexObject> complexObjects = retrieveAllComplexObjects2();
    return complexObjects.filter(pred);
}
Run Code Online (Sandbox Code Playgroud)

调用方看起来像这样:

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveObjectsFilteredBy2(cobj -> allowedTags.contains(cobj.getTag()))
            .collect(toList());
Run Code Online (Sandbox Code Playgroud)

现在,如果您仔细观察,您会发现retrieve-filter方法根本没有添加任何值,因此您不妨将其内联到调用者中:

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

当然,根据调用者想要做什么,它可能不想将结果收集到列表中;forEach()它可能想用或其他方式处理结果。

现在您仍然可以将过滤器分解为它自己的方法,以进行测试/调试,并且您可以使用方法引用:

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

    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(this::cobjFilter)
            .collect(toList());
Run Code Online (Sandbox Code Playgroud)

如果您不希望过滤器内置允许的标签,您可以将其从谓词更改为返回谓词的高阶函数:

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

    List<String> allowedTags = ... ;
    List<ComplexObject> result =
        retrieveAllComplexObjects2()
            .filter(cobjFilter(allowedTags))
            .collect(toList());
Run Code Online (Sandbox Code Playgroud)

这些变体中哪一个最有意义取决于您的应用程序的外观以及您在过滤中需要什么样的动态性。