在Java中,通过多个字段进行过滤的最佳方法是什么?

7

我有一个包含10个字段的实体:

Class Details{
  String item; 
  String name; 
  String type;
  String origin; 
  String color; 
  String quality;
  String country;
  String quantity;
  Boolean availability;
  String price;
 }

我有一个提供列表的restful端点。我希望用户能够为每个字段提供搜索过滤器。 当前,我为每个字段使用QueryParam,然后使用java8流进行过滤:

List<Detail> details;
details.stream().filter(detail-> detail.getItem()==item).filter(detail-> detail.getName()==name).....collect(Collectors.toList());

如果我有其他50个带有多个字段的类需要进行筛选,是否有一种通用的方法呢?


3
这可能是反射很有用的罕见情况之一:所以你可以使用反射“按名称”而不是像你的示例中手动编写所有属性。但我想知道,这个模型背后有多少设计,导致了50个类看起来像这样。 - GhostCat
1
不要使用“==”比较字符串。请使用“equals”方法或使用Objects.equals。参见https://dev59.com/DnRB5IYBdhLWcg3wyqEd。 - VGR
3个回答

10
您可以使用 .and().or() 来构建这样的谓词,从而定义一个应用所有所需检查的单个聚合谓词,而不是尝试链接 n.filter() 调用。这使得可以在运行时构建任意复杂的谓词。
// Note that you shouldn't normally use == on objects
Predicate<Detail> itemPredicate = d-> item.equals(d.getItem());
Predicate<Detail> namePredicate = d-> name.equals(d.getName());

details.stream()
    .filter(itemPredicate.and(namePredicate))
    .collect(Collectors.toList());

2
如果你想避免反射,可以考虑以下方法:
static enum DetailQueryParams {

    ITEM("item", d -> d.item),
    NAME("name", d -> d.name),
    TYPE("type", d -> d.type),
    ORIGIN("origin", d -> d.origin),
    COLOR("color", d -> d.color),
    QUALITY("quality", d -> d.quality),
    COUNTRY("country", d -> d.country),
    QUANTITY("quantity", d -> d.quantity),
    AVAILABILITY("availability", d -> d.availability),
    PRICE("price", d -> d.price);

    private String name;
    private Function<Detail, Object> valueExtractor;

    private DetailQueryParams(String name, 
            Function<Detail, Object> valueExtractor) {
        this.name = name;
        this.valueExtractor = valueExtractor;
    }

    public static Predicate<Detail> mustMatchDetailValues(
            Function<String, Optional<String>> queryParamGetter) {
        return Arrays.asList(values()).stream()
                .map(p -> queryParamGetter.apply(p.name)
                        .map(q -> (Predicate<Detail>) 
                                d -> String.valueOf(p.valueExtractor.apply(d)).equals(q))
                        .orElse(d -> true))
                .reduce(Predicate::and)
                .orElse(d -> true);
    }

}

假设您可以通过例如 request.getQueryParam(String name) 来访问查询参数,该方法返回一个 String 值或 null,那么使用以下代码调用它:

details.stream()
    .filter(DetailQueryParams.mustMatchDetailValues(
            name -> Optional.ofNullable(request.getQueryParam(name))))
    .collect(Collectors.toList());

该方法的基本作用是:

- for each possible query param
    - get its value from the request
    - if value is present build predicate which
        - gets field value from detail object and convert to string
        - check that both strings (queried and extracted) matches
    - if value is not present return predicate that always returns true
- combine resulting predicates using and
- use always true as fallback (which here never actually happens)

当然,这也可以扩展为根据实际值类型生成谓词,而不是比较字符串,以便通过 "priceMin" 和/或 "priceMax" 请求和处理范围等内容。

2
你也可以使用 Arrays.stream(values()) 代替 Arrays.asList(values()).stream()。你还可以将 .reduce(Predicate::and).orElse(d -> true) 简化为 .reduce(d -> true, Predicate::and)。 - srborlongan

1

有关Lambda的详细信息 -> 条件。因此,我们可以指定需要多少个。

details.stream().filter(detail-> detail.getItem()==item && detail.getName()==name && ...)

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接