按字符串顺序配置的排序逻辑

Sorting logic configured by string order

我有一个逗号分隔的字符串,其中包含对学生列表进行排序的排序标准:

Input:
String sortOrder = "height, weight, age"
List<student> students;

上面的每个元素都是学生对象上的比较器,它们使用一些复杂的逻辑对学生对象进行排序。我需要知道如何最好地转换出现在 sortOrder 字符串中的排序顺序,并按该顺序激活匹配的比较器。因此,对于上面的示例,高度比较器将首先 运行,最后是年龄。

您可以 HashMap 将这些词映射到 Comparator 对象。然后使用比较器进行排序

Map<String, Comparator<student>> comparators = new HashMap<>();

像这样将 Comparator 个对象添加到 comparators 之后:

comparators.put("height", Comparator.comparingDouble(student::getHeight));

如果您想连续执行不同的排序,只需浏览 sortOrder 中的单词并应用,例如

for (String comp : sortOrder.split(", "))
    Collections.sort(students, comparators.get(comp));

如果我理解正确的话,你想按身高、体重和年龄对学生列表进行排序。但是您希望这个 属性 列表是动态的。

这意味着我们需要实现一个自定义 Comparator,它适用于给定 class 的给定 属性。

第一个实现可能是创建一个 Map,其中每个字符串 属性 都映射到关联 Comparator。这将是一种干净的方法(请参阅@Manos Nikolaidis 的回答和 Misha 的评论,了解此实现)。

一个真正的动态解决方案是可能的,使用一点反射:首先检索具有给定 class 的给定名称的声明字段。它设置为可访问,因为该字段很可能是私有的。最后,返回一个 Comparator 比较每个学生该字段的值。此代码盲目地假设目标 属性 实际上是 Comparable(否则,我们为什么要使用此字段进行比较?)。

private static <U> Comparator<U> comparingProperty(String property, Class<U> clazz) {
    try {
        Field field = clazz.getDeclaredField(property);
        field.setAccessible(true);
        return Comparator.comparing(s -> {
            try {
                @SuppressWarnings("unchecked")
                Comparable<Object> comparable = (Comparable<Object>) field.get(s);
                return comparable;
            } catch (IllegalAccessException e) {
                throw new AssertionError(e);
            }
        });
    } catch (NoSuchFieldException e) {
        throw new AssertionError(e);
    }
}

一旦有了这个实用比较器,我们就可以轻松地对学生列表进行排序。通过围绕 "," 拆分并修剪结果来创建排序属性流。然后,每个 属性 映射到其对应的 Comparator,最后,通过将所有比较器与 Comparator::thenComparing:

组合在一起来减少该流
students.sort(Stream.of(sortOrder.split(","))
                    .map(String::trim)
                    .map(s -> comparingProperty(s, Student.class))
                    .reduce(Comparator::thenComparing)
                    .get()
             );