按字符串顺序配置的排序逻辑
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()
);
我有一个逗号分隔的字符串,其中包含对学生列表进行排序的排序标准:
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()
);