向比较器的比较方法添加额外的规则
Add additional rules to the compare method of a Comparator
我目前有一个代码片段,其中 returns 个列表的字符串按升序排列:
Collections.sort(myList, new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
return o1.aString.compareTo(o2.aString);
}
});
虽然可行,但我想在顺序中添加一些自定义 "rules" 以将某些字符串放在前面。例如:
if(aString.equals("Hi")){
// put string first in the order
}
if(aString begins with a null character, e.g. " ") {
// put string after Hi, but before the other strings
}
// So the order could be: Hi, _string, a_string, b_string, c_string
是否可以像这样使用 Comparator
自定义列表的排序?
是的,这是可能的,您可以完全控制 compareTo() 方法。两件事:
- 使用 String#equals 而不是 == 来比较字符串
- 请确保针对异常情况检查 compareTo 的两个参数。
一种具体的实现方式,其中一些词总是在前面,而一些词总是在最后,在例外中定义了顺序:
Map<String, Integer> exceptionMap = new HashMap<>();
exceptionMap.put("lowest", -2);
exceptionMap.put("second_lowest", -1);
exceptionMap.put("second_highest", 1);
exceptionMap.put("highest", 2);
public int compareToWithExceptionMap(String s1, String s2) {
int firstExceptional = exceptionMap.getOrDefault(s1, 0);
int secondExceptional = exceptionMap.getOrDefault(s2, 0);
if (firstExceptional == 0 && secondExceptional == 0) {
return s1.compareTo(s2);
}
return firstExceptional - secondExceptional;
}
这是可能的。
使用 Java 8 个特征
您可以将一个函数传递给 Comparator.comparing
方法来定义您的规则。请注意,我们只是 return 整数,元素的最低整数应该排在第一位。
Comparator<MyClass> myRules = Comparator.comparing(t -> {
if (t.aString.equals("Hi")) {
return 0;
}
if (t.aString.startsWith(" ")) {
return 1;
}
else {
return 2;
}
});
如果您希望剩余的元素按字母顺序排序,您可以使用 thenComparing(Comparator.naturalOrder())
,如果您的 class 实现了 Comparable
。否则,您应该先提取排序键:
Collections.sort(myList, myRules.thenComparing(Comparator.comparing(t -> t.aString)));
请注意,实际的特定数字 returned 并不重要,重要的是排序时较低的数字在较高的数字之前,因此如果总是将字符串 "Hi" 放在第一位,那么相应的数字应该是最低的returned(在我的例子中是0)。
使用 Java <= 7 个功能(Android API 21 级兼容)
如果Java 8 项功能您无法使用,那么您可以这样实现:
Comparator<MyClass> myRules = new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
int order = Integer.compare(getOrder(o1), getOrder(o2));
return (order != 0 ? order : o1.aString.compareTo(o2.aString));
}
private int getOrder(MyClass m) {
if (m.aString.equals("Hi")) {
return 0;
}
else if (m.aString.startsWith(" ")) {
return 1;
}
else {
return 2;
}
}
};
并这样称呼它:
Collections.sort(list, myRules);
其工作原理如下:首先,两个接收到的字符串都映射到您的自定义规则集并相互相减。如果两者不同,则操作 getOrder(o1) - getOrder(o2)
确定比较。否则,如果两者相同,则按字典顺序进行比较。
来自 MC Emperor 的 非常好 (+1),因为它满足了 OP 不使用 Java 8 个 API 的要求。它还使用一种简洁的内部函数技术(getOrder
方法)将条件映射到小整数值以实现一级比较。
这是一个使用 Java 8 构造的替代方案。它假定 MyClass
有一个 getString
方法可以完成显而易见的事情。
Collections.sort(myList,
Comparator.comparing((MyClass mc) -> ! mc.getString().equals("Hi"))
.thenComparing(mc -> ! mc.getString().startsWith(" "))
.thenComparing(MyClass::getString));
在您习惯这种风格之前,这是非常不透明的。关键见解是提供给 Comparator.comparing
和 Comparator.thenComparing
的 "extractor" 函数通常只是提取一个字段,但它可以是到任何其他值的一般映射。如果该值是 Comparable 则不需要为其提供额外的 Comparator。在这种情况下,提取器函数是一个布尔表达式。这被装箱到一个布尔值,结果是 Comparable。由于 false
订单在 true
之前,我们需要取反布尔表达式。
另请注意,我必须为 lambda 参数提供显式类型声明,因为类型推断通常不适用于链式比较器情况,例如这个。
我目前有一个代码片段,其中 returns 个列表的字符串按升序排列:
Collections.sort(myList, new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
return o1.aString.compareTo(o2.aString);
}
});
虽然可行,但我想在顺序中添加一些自定义 "rules" 以将某些字符串放在前面。例如:
if(aString.equals("Hi")){
// put string first in the order
}
if(aString begins with a null character, e.g. " ") {
// put string after Hi, but before the other strings
}
// So the order could be: Hi, _string, a_string, b_string, c_string
是否可以像这样使用 Comparator
自定义列表的排序?
是的,这是可能的,您可以完全控制 compareTo() 方法。两件事:
- 使用 String#equals 而不是 == 来比较字符串
- 请确保针对异常情况检查 compareTo 的两个参数。
一种具体的实现方式,其中一些词总是在前面,而一些词总是在最后,在例外中定义了顺序:
Map<String, Integer> exceptionMap = new HashMap<>();
exceptionMap.put("lowest", -2);
exceptionMap.put("second_lowest", -1);
exceptionMap.put("second_highest", 1);
exceptionMap.put("highest", 2);
public int compareToWithExceptionMap(String s1, String s2) {
int firstExceptional = exceptionMap.getOrDefault(s1, 0);
int secondExceptional = exceptionMap.getOrDefault(s2, 0);
if (firstExceptional == 0 && secondExceptional == 0) {
return s1.compareTo(s2);
}
return firstExceptional - secondExceptional;
}
这是可能的。
使用 Java 8 个特征
您可以将一个函数传递给 Comparator.comparing
方法来定义您的规则。请注意,我们只是 return 整数,元素的最低整数应该排在第一位。
Comparator<MyClass> myRules = Comparator.comparing(t -> {
if (t.aString.equals("Hi")) {
return 0;
}
if (t.aString.startsWith(" ")) {
return 1;
}
else {
return 2;
}
});
如果您希望剩余的元素按字母顺序排序,您可以使用 thenComparing(Comparator.naturalOrder())
,如果您的 class 实现了 Comparable
。否则,您应该先提取排序键:
Collections.sort(myList, myRules.thenComparing(Comparator.comparing(t -> t.aString)));
请注意,实际的特定数字 returned 并不重要,重要的是排序时较低的数字在较高的数字之前,因此如果总是将字符串 "Hi" 放在第一位,那么相应的数字应该是最低的returned(在我的例子中是0)。
使用 Java <= 7 个功能(Android API 21 级兼容)
如果Java 8 项功能您无法使用,那么您可以这样实现:
Comparator<MyClass> myRules = new Comparator<MyClass>() {
@Override
public int compare(MyClass o1, MyClass o2) {
int order = Integer.compare(getOrder(o1), getOrder(o2));
return (order != 0 ? order : o1.aString.compareTo(o2.aString));
}
private int getOrder(MyClass m) {
if (m.aString.equals("Hi")) {
return 0;
}
else if (m.aString.startsWith(" ")) {
return 1;
}
else {
return 2;
}
}
};
并这样称呼它:
Collections.sort(list, myRules);
其工作原理如下:首先,两个接收到的字符串都映射到您的自定义规则集并相互相减。如果两者不同,则操作 getOrder(o1) - getOrder(o2)
确定比较。否则,如果两者相同,则按字典顺序进行比较。
来自 MC Emperor 的 getOrder
方法)将条件映射到小整数值以实现一级比较。
这是一个使用 Java 8 构造的替代方案。它假定 MyClass
有一个 getString
方法可以完成显而易见的事情。
Collections.sort(myList,
Comparator.comparing((MyClass mc) -> ! mc.getString().equals("Hi"))
.thenComparing(mc -> ! mc.getString().startsWith(" "))
.thenComparing(MyClass::getString));
在您习惯这种风格之前,这是非常不透明的。关键见解是提供给 Comparator.comparing
和 Comparator.thenComparing
的 "extractor" 函数通常只是提取一个字段,但它可以是到任何其他值的一般映射。如果该值是 Comparable 则不需要为其提供额外的 Comparator。在这种情况下,提取器函数是一个布尔表达式。这被装箱到一个布尔值,结果是 Comparable。由于 false
订单在 true
之前,我们需要取反布尔表达式。
另请注意,我必须为 lambda 参数提供显式类型声明,因为类型推断通常不适用于链式比较器情况,例如这个。