获取java.lang.illegalArgumentException:比较方法违反了它的一般契约!在使用 Comparator 对列表进行排序时
Getting java.lang.illegalArgumentException : comparison method violates its general contract ! while using Comparator to sort a list
我试图在 Java 中以特定方式对列表进行排序,我发现 Comparator
是一个很好的方法。
给大家分享一个问题的伪代码
我有一个 DTO 列表,假设我想按特定顺序按 属性(String) 对它进行排序,例如,以 "Hi"
开头的属性应该在最上面,而休息应该在下面。
这是我的伪代码:
list.sort(new Comparator<myDto>(){
@Override
public int compare(myDto o1, myDto o2){
if(o1.getProperty1() != null && o2.getProperty1() == null)
return -1;
else if(o1.getProperty1() == null && o2.getProperty1() != null)
return 1;
else if(o1.getProperty1().startsWith("Hi") && o2.getProperty1().startsWith("Hi"))
return 0;
else if(o1.getProperty1().startsWith("Hi") && !o2.getProperty1().startsWith("Hi"))
return -1;
return 1;
}
});
我使用了 4、5 个我自己创建的 DTO 来测试,但是当我注入一个 14k DTO 的文件时,我得到了 java.lang.illegalArgumentException
.
有什么想法吗?
将最终的 return 1
更改为 return o1.getProperty1().compareTo(o2.getProperty1())
JVM 可以比较元素 a, b
或 b, a
- 如果您只是 return 1 在最后那么你将总是违反总合同。
在您的文本中,您说您希望那些以 "Hi" 开头的对象在(小于)其他对象之前。此外,您的代码暗示您希望末尾为空值(高于其他任何内容)。因此,您的比较器必须考虑 9 种情况(Hi、非 Hi、o1 为 null 与 Hi、非 Hi、o2 为 null)和 return 以下值:
o1=Hi: 0,-1,-1 for o2=Hi,non-Hi,null
o1=non-Hi: 1, 0,-1 for o2=Hi,non-Hi,null
o1=null: 1, 1, 0 for o2=Hi,non-Hi,null
您的代码不符合 table,例如对于 non-Hi/non-Hi 你总是 return 1 而不是 0,例如在做 compare("Peter","John")
和 compare("John","Peter")
时。正如 Elliot 已经指出的那样,compare(a,b)
和 compare(b,a)
两者 return 0 或 return 的结果都具有相反的符号,这一点至关重要。
P.S。 table 假定您不关心三个组中的顺序。如果你想要一个,你可以用例如的结果替换零词汇比较器。
你必须考虑 a.compareTo(b) == -b.compareTo(a)
。您的最后一个测试只是假设如果以 "Hi" 开头,您可以 return 1
但这违反了上述规则。你可以做的是这样的。
list.sort((o1, o2) -> {
String o1p1 = o1.getProperty1(), o2p1 = o2.getProperty1();
boolean isNull1 = o1p1 == null, isNull2 = o2p1 == null;
if (isNull1)
return isNull2 ? 0 : -1;
else if (isNull2)
return +1;
boolean o1p1hi = o1p1.startsWith("Hi"), o2p1hi = o1p1.startsWith("Hi");
if (o1p1hi)
return o2p1hi ? 0 : -1;
else if (o2p1hi)
return +1;
return o1p1.compareTo(o2p1);
});
在其他答案中,您可以找到为什么 Comparator
不起作用的解释 - 简而言之,您最后返回 1
会使 Comparator
不一致(compare(a,b) != -compare(b,a)
).
直接 Comparator
实现很难读写。这就是为什么在 Java 8 中,您可以使用各种 Comparator
methods.
的功能方法
将您的 Comparator
转换为函数方法会产生:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.comparing(
property1 -> !property1.startsWith("Hi")
));
Comparator<MyDto> myDtoComparator = Comparator.comparing(MyDto::getProperty1, property1Comparator);
我相信这种方法比所有直接 Comparator
实现更具可读性。
PS。如果你想获得与 中相同的结果(另外按自然顺序对没有前缀 "Hi"
的字符串进行排序),你需要以下 property1Comparator
:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.<String, Boolean>comparing(
property1 -> !property1.startsWith("Hi")
).thenComparing(Comparator.naturalOrder()));
我试图在 Java 中以特定方式对列表进行排序,我发现 Comparator
是一个很好的方法。
给大家分享一个问题的伪代码
我有一个 DTO 列表,假设我想按特定顺序按 属性(String) 对它进行排序,例如,以 "Hi"
开头的属性应该在最上面,而休息应该在下面。
这是我的伪代码:
list.sort(new Comparator<myDto>(){
@Override
public int compare(myDto o1, myDto o2){
if(o1.getProperty1() != null && o2.getProperty1() == null)
return -1;
else if(o1.getProperty1() == null && o2.getProperty1() != null)
return 1;
else if(o1.getProperty1().startsWith("Hi") && o2.getProperty1().startsWith("Hi"))
return 0;
else if(o1.getProperty1().startsWith("Hi") && !o2.getProperty1().startsWith("Hi"))
return -1;
return 1;
}
});
我使用了 4、5 个我自己创建的 DTO 来测试,但是当我注入一个 14k DTO 的文件时,我得到了 java.lang.illegalArgumentException
.
有什么想法吗?
将最终的 return 1
更改为 return o1.getProperty1().compareTo(o2.getProperty1())
JVM 可以比较元素 a, b
或 b, a
- 如果您只是 return 1 在最后那么你将总是违反总合同。
在您的文本中,您说您希望那些以 "Hi" 开头的对象在(小于)其他对象之前。此外,您的代码暗示您希望末尾为空值(高于其他任何内容)。因此,您的比较器必须考虑 9 种情况(Hi、非 Hi、o1 为 null 与 Hi、非 Hi、o2 为 null)和 return 以下值:
o1=Hi: 0,-1,-1 for o2=Hi,non-Hi,null
o1=non-Hi: 1, 0,-1 for o2=Hi,non-Hi,null
o1=null: 1, 1, 0 for o2=Hi,non-Hi,null
您的代码不符合 table,例如对于 non-Hi/non-Hi 你总是 return 1 而不是 0,例如在做 compare("Peter","John")
和 compare("John","Peter")
时。正如 Elliot 已经指出的那样,compare(a,b)
和 compare(b,a)
两者 return 0 或 return 的结果都具有相反的符号,这一点至关重要。
P.S。 table 假定您不关心三个组中的顺序。如果你想要一个,你可以用例如的结果替换零词汇比较器。
你必须考虑 a.compareTo(b) == -b.compareTo(a)
。您的最后一个测试只是假设如果以 "Hi" 开头,您可以 return 1
但这违反了上述规则。你可以做的是这样的。
list.sort((o1, o2) -> {
String o1p1 = o1.getProperty1(), o2p1 = o2.getProperty1();
boolean isNull1 = o1p1 == null, isNull2 = o2p1 == null;
if (isNull1)
return isNull2 ? 0 : -1;
else if (isNull2)
return +1;
boolean o1p1hi = o1p1.startsWith("Hi"), o2p1hi = o1p1.startsWith("Hi");
if (o1p1hi)
return o2p1hi ? 0 : -1;
else if (o2p1hi)
return +1;
return o1p1.compareTo(o2p1);
});
在其他答案中,您可以找到为什么 Comparator
不起作用的解释 - 简而言之,您最后返回 1
会使 Comparator
不一致(compare(a,b) != -compare(b,a)
).
直接 Comparator
实现很难读写。这就是为什么在 Java 8 中,您可以使用各种 Comparator
methods.
将您的 Comparator
转换为函数方法会产生:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.comparing(
property1 -> !property1.startsWith("Hi")
));
Comparator<MyDto> myDtoComparator = Comparator.comparing(MyDto::getProperty1, property1Comparator);
我相信这种方法比所有直接 Comparator
实现更具可读性。
PS。如果你想获得与 "Hi"
的字符串进行排序),你需要以下 property1Comparator
:
Comparator<String> property1Comparator = Comparator.nullsLast(Comparator.<String, Boolean>comparing(
property1 -> !property1.startsWith("Hi")
).thenComparing(Comparator.naturalOrder()));