[英]Why does this compareTo() method lead to a contract violation while sorting?
I have a list of filenames and want to compare these in the following order: 我有一个文件名列表,并想按以下顺序进行比较:
So I am using the following compareTo
method for one of my Java classes: 因此,我对我的一个Java类使用了以下
compareTo
方法:
public class DownloadFile implements Comparable<DownloadFile>
{
// custom code ...
@Override
public int compareTo(DownloadFile other)
{
if(other == null)
throw new NullPointerException("Object other must not be null");
// special cases -- .rar vs .par2 etc.
String thisStr = filename.toLowerCase();
String oStr = other.getFilename().toLowerCase();
if(thisStr.endsWith(".rar") && oStr.matches(".*\\.r[0-9]{2,}$"))
return -1;
if(thisStr.matches(".*\\.r[0-9]{2,}$") && oStr.endsWith(".rar"))
return 1;
if(!thisStr.endsWith(".par2") && oStr.endsWith(".par2"))
return -1;
if(thisStr.endsWith(".par2") && !oStr.endsWith(".par2"))
return 1;
// normal comparison based on filename strings
return thisStr.compareTo(oStr);
}
}
However, on some data this leads to the following execption: 但是,在某些数据上,这导致以下执行:
Exception in thread "Thread-12" java.lang.IllegalArgumentException: Comparison method violates its general contract!
I tried to understand what I am missing here, but I can't find the issue. 我试图了解我在这里缺少的内容,但是找不到问题。
Can you spot where I am violating the contract? 你能发现我违反合同的地方吗?
PS: If I comment out the second two if
s, then the exeception is still thrown. PS:如果我将后两个
if
注释掉,那么这种理解仍然会抛出。 So the problem lies with the first two if
s. 因此,问题出在前两个
if
s上。
It is not transitive. 它不是可传递的。
Linear ordering of elements is not possible. 元素的线性排序是不可能的。
Proof by counterexample. 通过反例证明。
Say you have got 3 DownloadFile
s ( c
, b
, a
) with names in lowercase: 假设您有3个
DownloadFile
s( c
, b
, a
),它们的名称均小写:
c.par2
b.notpar2
a.par2
To simplify I will use <
for linear ordering and names in lowercase. 为了简化,我将使用
<
进行线性排序并以小写形式命名。
c.par2 < b.notpar2
and b.notpar2 < a.par2
, but it is not true that c.par2 < a.par2
. c.par2 < b.notpar2
和b.notpar2 < a.par2
,但它是不正确的c.par2 < a.par2
。
This relation is not transitive . 这种关系不是传递的 。
In logic... it would be like: 从逻辑上来说……
cRb
and bRa
, but it is not true that cRa
. cRb
和bRa
,但它是不正确的cRa
。
All you have to do is to answer how to order your files linearly... 您所要做的就是回答如何线性排列文件...
I would go for something like this: 我会去这样的事情:
if(aMethodOnThis < aMethodOnOther) {
return -1; //or 1
}
if(aCompletelyDifferentCriterium) {
//...
}
return 0; //or return thisFileName.compareTo(otherFileName);
The return 0
at the end is quite important, because it returned for indistinguishable files. 最后的
return 0
非常重要,因为对于无法区分的文件, return 0
。
In that case: 在这种情况下:
public class DownloadFile implements Comparable<DownloadFile>{
String filename;
DownloadFile(String filename) {
this.filename = filename;
}
public String getFilename() {
return this.filename;
}
@Override
public String toString() {
return this.getFilename();
}
@Override
public int compareTo(DownloadFile downloadFile) {
String thisStr = this.filename.toLowerCase();
String oStr = downloadFile.getFilename().toLowerCase();
if(thisStr.endsWith(".rar")) {
if(!oStr.endsWith(".rar"))
return -1;
}
if(oStr.endsWith(".rar")) {
if(!thisStr.endsWith(".rar"))
return 1;
}
if(thisStr.matches(".*\\.r[0-9]{2,}$")) {
if(!oStr.matches(".*\\.r[0-9]{2,}$"))
return -1;
}
if(oStr.matches(".*\\.r[0-9]{2,}$")) {
if(!thisStr.matches(".*\\.r[0-9]{2,}$"))
return 1;
}
if(thisStr.endsWith(".par2")) {
if(!oStr.endsWith(".par2"))
return -1;
}
if(oStr.endsWith(".par2")) {
if(!thisStr.endsWith(".par2"))
return 1;
}
return thisStr.compareTo(oStr);
}
public static void main(String[] args) {
List<DownloadFile> fileList = new ArrayList<>();
fileList.add(new DownloadFile("a.rar"));
fileList.add(new DownloadFile("b.rar"));
fileList.add(new DownloadFile("a.r01"));
fileList.add(new DownloadFile("b.r01"));
fileList.add(new DownloadFile("a.r10"));
fileList.add(new DownloadFile("b.r10"));
fileList.add(new DownloadFile("a.par2"));
fileList.add(new DownloadFile("b.par2"));
fileList.add(new DownloadFile("a.other"));
fileList.add(new DownloadFile("b.other"));
Collections.shuffle(fileList);
Collections.sort(fileList);
System.out.println(fileList);
}
}
To make it shorter Predicate<String>
from Java 8 comes in handy ;) 为了使它更短,来自Java 8的
Predicate<String>
派上用场了;)
@Override
public int compareTo(DownloadFile downloadFile) {
String thisStr = this.filename.toLowerCase();
String oStr = downloadFile.getFilename().toLowerCase();
List<Predicate<String>> conditionList = new ArrayList<>();
conditionList.add(s -> s.endsWith(".rar"));
conditionList.add(s -> s.matches(".*\\.r[0-9]{2,}$"));
conditionList.add(s -> s.endsWith(".par2"));
for(Predicate<String> condition : conditionList) {
int orderForCondition =
conditionHelper(thisStr, oStr, condition);
if(orderForCondition != 0)
return orderForCondition;
}
return thisStr.compareTo(oStr);
}
private int conditionHelper(String firstStr, String secondStr,
Predicate<String> condition) {
if(condition.test(firstStr))
if(!condition.test(secondStr))
return -1;
if(condition.test(secondStr))
if(!condition.test(firstStr))
return 1;
return 0;
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.