如何创建具有动态规则的比较器?

How to create a comparator with dynamic rules?

我需要根据按优先级排序的过滤器列表对项目列表进行排序。但是,这些过滤器来自 API 请求正文,因此它们可以更改。

我有一个过滤器class

public class Filter {
  private String fieldName;
  private String order;

  // Getters and Setters...
}

一些过滤器对象

Filter filter1 = new Filter("price", "desc");
Filter filter2 = new Filter("size", "asc");

我的项目 class 是这样的:

public class Item {
  private String productName;
  private double size;
  private double price;

  // Getters and Setters...
}

然后我必须像这样对项目进行排序:

如果 Item.price 等于下一个项目,比较它们的大小,依此类推...

我已经尝试为每个过滤器创建一个比较器,但我无法将它们链接起来,因此每个过滤器都自行对列表进行排序,而不考虑以前的排序方法(有时会颠倒整个列表).

我也尝试在 Item class 上实现 Comparable 接口,但是接口方法 compareTo 只接受一个参数(下一个 Item),而不接受规则列表。

所以给定一个项目列表,例如

List<Item> items = new ArrayList<Item>(
  new Item("foo", 10.0, 5.0),
  new Item("bar", 6.0, 15.0),
  new Item("baz", 7.0, 5.0)
);

还有一个过滤器列表,例如

List<Filter> filters = new ArrayList<Filter>(
  new Filter("price", "desc"),
  new Filter("size", "asc")
);

我希望结果是

List<Item> sortedItems = new ArrayList<Item>(
  new Item("bar", 6.0, 15.0),
  new Item("baz", 7.0, 5.0),
  new Item("foo", 10.0, 5.0)
);

你能帮帮我吗?提前致谢!

重要提示:我对字段本身的比较没有问题。我的问题是制作一个动态比较器,它根据过滤器列表更改其比较。

给定 price-based 和 size-based 初始实现,您可以编写比较器。

以下方法为给定过滤器创建项目比较器:

//Preferring static implementations to reflection-based ones
//if there are too many fields, you may want to use a Map<String, Comparator<Item>>
static Comparator<Item> priceComparator = Comparator.comparing(Item::getPrice);
static Comparator<Item> sizeComparator = Comparator.comparingDouble(Item::getSize);

private static Comparator<Item> itemComparator(Filter filter) {
    Comparator<Item> comparator = "price".equals(filter.getFieldName()) ? 
                                     priceComparator : sizeComparator;

    if ("desc".equals(filter.getOrder()))
        return comparator.reversed();

    return comparator;
}

由此您可以从过滤器列表中链接比较器:

public static void main(String args[]) {

    Comparator<Item> comparator = itemComparator(filters.get(0));
    for (Filter f : filters.subList(1, filters.size())) {
        comparator = comparator.thenComparing(itemComparator(f));
    }

    items.sort(comparator);
}

测试后,我得到以下(预期)输出:

[[productName=bar, size=6.0, price=15.0],
 [productName=baz, size=7.0, price=5.0],
 [productName=foo, size=10.0, price=5.0]]

我相信以下内容应该会推动您在为给定变量过滤器比较的项目链接比较器时朝着正确的方向前进。它使用反射来调用 getter 并假设比较是双精度的。

如果不能保证它们是双打,则调整反射投射以投射到适用于所有用例的东西。

PropertyUtils.getProperty() 是 Apache Commons BeanUtils 的一部分,可以替换为您选择获取值的方式,无论是通过反射还是静态比较器。

public class Filter {

    // properties, constructors, getters, setters ...

    public Comparator<Item> itemComparator() {
        return (item1, item2) -> {
            Double val1 = (Double) PropertyUtils.getProperty(item1, fieldName);
            Double val2 = (Double) PropertyUtils.getProperty(item2, fieldName);
            return (order.equals("asc") ? val1.compareTo(val2) : val2.compareTo(val1);
        };
    }

    public static Comparator<Item> chainedItemComparators(List<Filter> filters) {
        return filters.stream()
            .map(Filter::itemComparator)
            .reduce((item1, item2) -> 0, (f1, f2) -> f1.thenComparing(f2));
    }
}

然后使用链式比较器:

public static void main(String[] args) {
    List<Filter> filters = new ArrayList<>(Arrays.asList(
        new Filter("price", "desc"),
        new Filter("size", "asc")
    ));
    List<Item> items = new ArrayList<>(Arrays.asList(
        new Item("bar", 6.0, 15.0),
        new Item("baz", 7.0, 5.0),
        new Item("foo", 10.0, 5.0)
    ));
    items.sort(Filter.chainedItemComparators(filters));
}