压测时发现的报错,原因是自定义的比较器没有满足可逆比较。
Comparator接口的compare方法文档中有标注:The implementor must ensure that sgn(compare(x, y)) == -sgn(compare(y, x)) for all x and y.
下面用单元测试还原一下报错场景:
public static class IntegerComparator implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return (o1 > o2) ? 1 : -1;
}
}
@Test
public void testIntegerComparator() {
Integer[] arr =
{0, 0, 0, 0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 2, 1, 0, 0, 0, 2, 0, 0, 3};
List<Integer> list = Arrays.asList(arr);
Collections.sort(list, new IntegerComparator());
}
/*
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:899)
at java.util.TimSort.mergeAt(TimSort.java:516)
at java.util.TimSort.mergeCollapse(TimSort.java:441)
at java.util.TimSort.sort(TimSort.java:245)
at java.util.Arrays.sort(Arrays.java:1438)
at java.util.Arrays$ArrayList.sort(Arrays.java:3895)
at java.util.Collections.sort(Collections.java:175)
*/
问题在于,return (o1 > o2) ? 1 : -1;
没有考虑相等的情况,compare(1, 1)
(-1) != -compare(1, 1)
(1)不符合接口要求,最后会在比较时抛错。
挑选最喜欢的水果,按照大小、成本、采摘时间依次进行排序,可根据PreferSize决定更喜欢大的还是小的。
Collections.sort()
默认升序排序,排序后的list.get(0)
是排序最小的
public class Fruit {
// 水果名
private String name;
// 成本
private String cost;
// 大小
private String size;
// 采摘时间
private Long pickTime;
}
public enum PreferSize {
BIG("大", "BIG"),
SMALL("小", "SMALL");
private final String name;
private final String code;
PreferSize(String name, String code) {
this.name = name;
this.code = code;
}
}
public class BestFruitComparator implements Comparator<Fruit> {
private final PreferSize preferSize;
public BestFruitComparator(PreferSize preferSize) {
this.preferSize = preferSize;
}
@Override
public int compare(Fruit o1, Fruit o2) {
BigDecimal s1 = new BigDecimal(o1.getSize());
BigDecimal s2 = new BigDecimal(o2.getSize());
int result = s1.compareTo(s2);
if (result == 0) {
BigDecimal b1 = new BigDecimal(o1.getCost());
BigDecimal b2 = new BigDecimal(o2.getCost());
result = b1.compareTo(b2);
if (result == 0) {
// 后采摘的比较新鲜 排在前面
result = -(o1.getPickTime().compareTo(o2.getPickTime()));
}
return result;
} else if (result < 0) {
// PreferSize为BIG Size越大越好
return (PreferSize.BIG == preferSize) ? 1 : -1;
} else {
return (PreferSize.BIG == preferSize) ? -1 : 1;
}
}
}
构造单元测试
@Test
public void testBestFruitComparator() throws InterruptedException {
Fruit apple0 = new Fruit("apple0", "5.5", "50", 100L);
Fruit apple1 = new Fruit("apple1", "6.5", "60", 101L);
Fruit apple2 = new Fruit("apple2", "6.5", "50", 102L);
List<Fruit> fruits = new ArrayList<>();
fruits.add(apple0);
fruits.add(apple1);
fruits.add(apple2);
Collections.sort(fruits, new BestFruitComparator(PreferSize.BIG));
for (Fruit fruit : fruits) {
System.out.println(fruit);
}
Thread.sleep(100);
Collections.sort(fruits, new BestFruitComparator(PreferSize.SMALL));
for (Fruit fruit : fruits) {
System.err.println(fruit);
}
}
/*
Fruit{name='apple1', cost='6.5', size='60', pickTime=101}
Fruit{name='apple0', cost='5.5', size='50', pickTime=100}
Fruit{name='apple2', cost='6.5', size='50', pickTime=102}
Fruit{name='apple0', cost='5.5', size='50', pickTime=100}
Fruit{name='apple2', cost='6.5', size='50', pickTime=102}
Fruit{name='apple1', cost='6.5', size='60', pickTime=101}
*/