根据数量波动的条件过滤列表

时间:2019-01-29 23:00:16

标签: java arraylist filter java-stream

我想编写代码,将能够使用多种条件过滤对象列表,但我希望条件数量波动(有时过滤1个字段,有时过滤所有3个字段)。

在我的情况下,我希望用户可以选择为某个字段选择“任意”,但仍将过滤器应用于其他字段。 在以下情况下,用户可以通过字段x, y & z过滤列表,但随后他们也可以将过滤器之一更改为“ any”,从而在实际过滤列表时不需要该过滤器。 getFilteredList方法从每个组合框中获取String的值,并使用每个String来过滤列表,除非String为'any',则它将接受任何{{1 }}。

combo boxes uses to input a filter option for each field

我在下面编写了代码,可以满足这种情况,但将来添加更多过滤器时,效率将越来越低。 (2个过滤器导致4个return语句,3个过滤器导致8个以此类推...)。

String

private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z ){ if(x.equals("any") && y.equals("any") && z.equals("any")) { return originalList; } if(x.equals("any") && y.equals("any") && !z.equals("any")) { return originalList.stream().filter(item -> item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new)); } if(x.equals("any") && !y.equals("any") && z.equals("any")) { return originalList.stream().filter(item -> item.getY().equals(y)).collect(Collectors.toCollection(ArrayList::new)); } if(x.equals("any") && !y.equals("any") && !z.equals("any")) { return originalList.stream().filter(item -> item.getY().equals(y)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new)); } if(!x.equals("any") && y.equals("any") && z.equals("any")) { return originalList.stream().filter(item -> item.getX().equals(x)).collect(Collectors.toCollection(ArrayList::new)); } if(!x.equals("any") && y.equals("any") && !z.equals("any")) { return originalList.stream().filter(item -> item.getX().equals(x)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new)); } if(!x.equals("any") && !y.equals("any") && z.equals("any")) { return originalList.stream().filter(item -> item.getX().equals(x)&& item.getY().equals(y)).collect(Collectors.toCollection(ArrayList::new)); } if(!x.equals("any") && !y.equals("any") && !z.equals("any")) { return originalList.stream().filter(item -> item.getX().equals(x)&& item.getY().equals(y)&& item.getZ().equals(z)).collect(Collectors.toCollection(ArrayList::new)); } return originalList; } 如下所示:

Model

下面的代码将返回一个列表,其中public class Model { private String x; private String y; private String z; public String getX() { return x; } public String getY() { return y; } public String getZ() { return z; } } 字段等于'red',其中x字段等于'medium'并且y字段可以是任何东西。

z

我不确定是否有更简便的方法来执行private static ArrayList<Model> items = new ArrayList<>(); public static void main(String[] args) { ArrayList<Model> filteredList = getFilteredList(items, "red", "medium", "any"); } 方法,我已经搜索了很多,但是找不到任何更简单/更有效的实现。将来,我希望允许使用10个以上的过滤器,并且始终可以选择“ any”,但是我目前的解决方案太长了,根本就不会很优雅。如果有人能提供有关如何用不同数量的过滤器优雅地过滤列表的答案,将不胜感激。

谢谢。

4 个答案:

答案 0 :(得分:2)

您只需在流中为每个要过滤的String及其相应的Model属性链接过滤调用。

如果该属性的过滤器为stringFilter或该过滤器等于相应"any"的值,则Model BiFunction确保将模型包含在结果流中领域。
由于每个过滤器都作用于最后一个过滤器的输出,因此我们可以确保返回的List中不会包含所有不符合所有所需条件的Model

public List<Model> getFilteredList(List<Model> originalList, String x, String y, String z) {

    final BiPredicate<String, Supplier<String>> stringFilter = (filter, stringSupplier) ->
            filter.equals("any") || filter.equals(stringSupplier.get());

    return originalList.stream()
            .filter(model -> stringFilter.test(x, model::getX))
            .filter(model -> stringFilter.test(y, model::getY))
            .filter(model -> stringFilter.test(z, model::getZ))
            .collect(Collectors.toList());
}

Supplier<T>类型是java.util.function中定义的功能接口(仅具有一种方法的接口),当调用其get()方法时,它将返回类型T的实例。

当我们过滤流时,我们将其中一个字符串作为过滤依据,并将相应的Model getter传递给BiPredicate。该getter通过引用传递,并将作为我们Supplier的字符串源。

答案 1 :(得分:1)

您可以使用辅助方法使用给定的字段和值构造谓词,然后合并这些谓词并将其应用于流:

private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z ){
    Predicate<Model> filter = Stream.of(compare(Model::getX, x), compare(Model::getY, y), compare(Model::getZ, z))
            .filter(Objects::nonNull)
            .reduce(Predicate::and)
            .orElse($ -> true);

    return originalList.stream()
            .filter(filter)
            .collect(Collectors.toCollection(ArrayList::new));
}

private static Predicate<Model> compare(Function<Model, String> field, String value) {
    return value.equals("any") ? null : m -> field.apply(m).equals(value);
}

答案 2 :(得分:1)

在增加过滤条件数量时,这是一种线性而非指数增长的方法:

private static ArrayList<Model> getFilteredList(ArrayList<Model> originalList, String x, String y, String z){
    Predicate<Model> valueCheck, modelCheck = null;
    if ((valueCheck = (x.equals("any") ? null : item -> x.equals(item.getX()))) != null)
        modelCheck = valueCheck;
    if ((valueCheck = (y.equals("any") ? null : item -> y.equals(item.getY()))) != null)
        modelCheck = (modelCheck != null ? modelCheck.and(valueCheck) : valueCheck);
    if ((valueCheck = (z.equals("any") ? null : item -> z.equals(item.getZ()))) != null)
        modelCheck = (modelCheck != null ? modelCheck.and(valueCheck) : valueCheck);
    if (modelCheck == null)
        return originalList; // No filtering needed
    return originalList.stream().filter(modelCheck).collect(Collectors.toCollection(ArrayList::new));
}

它依赖于使用Predicate帮助方法来构建复合过滤器,也称为and(...)

答案 3 :(得分:1)

您可以使用

private static List<Model> getFilteredList(
    ArrayList<Model> originalList, String x, String y, String z ){

    if(Stream.of(x, y, z).allMatch("any"::equals)) return originalList;

    Stream<Model> s = originalList.stream();
    if(!"any".equals(x)) s = s.filter(m -> m.getX().equals(x));
    if(!"any".equals(y)) s = s.filter(m -> m.getY().equals(y));
    if(!"any".equals(z)) s = s.filter(m -> m.getZ().equals(z));
    return s.collect(Collectors.toList());
}

但是您应该对返回类型下定决心。显式的返回类型ArrayList表示调用方收到一个可变列表,但是当列表有时是原始列表而有时不是原始列表时(取决于过滤条件),这些修改具有未定义的语义,因为它们可能会影响原始的是否列出,这将是灾难性的。

所以您必须决定是否

  • 不应修改返回的列表(不要将其声明为ArrayList,也不要强制创建ArrayList),这就是上面的示例所做的或

  • 返回的列表应该是可变列表,而不影响源,然后取消针对所有{"any"

  • 返回原始列表的优化
  • 此后应该不使用原始列表,或者

  • 返回列表的修改应始终影响原始列表

对于c),无法在该方法内强制执行合同,对于d),将无法使用Stream API。因此对于c)或d),最好修改原始列表,例如

private static void filterList(ArrayList<Model> list, String x, String y, String z) {
    Predicate<Model> any = m -> false, effective = any;
    if(!"any".equals(x)) effective = m -> !m.getX().equals(x);
    if(!"any".equals(y)) effective = effective.or(m -> !m.getY().equals(y));
    if(!"any".equals(z)) effective = effective.or(m -> !m.getZ().equals(z));
    if(effective != any) list.removeIf(effective);
}

对于语义没有任何疑问。