最近写排序用到的比较器时,碰到了类似如下的异常:
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:868)
at java.util.TimSort.mergeAt(TimSort.java:485)
at java.util.TimSort.mergeCollapse(TimSort.java:408)
at java.util.TimSort.sort(TimSort.java:214)
at java.util.TimSort.sort(TimSort.java:173)
at java.util.Arrays.sort(Arrays.java:659)
at java.util.Collections.sort(Collections.java:217)
自己代码大致如下所示,主要功能就是将获取的文件按照时间排序:
public ArrayList<File> getAllLogFiles(int flag) {
ArrayList<File> fileList = new ArrayList<File>();
//省略获取File的逻辑
........
if (fileList.size() > 1) {
//比较器,根据时间决定结果
Collections.sort(fileList, new Comparator<File>() {
@Override
public int compare(File lFile, File rFile) {
long val = lFile.lastModified() - rFile.lastModified();
if (val > 0) {
return -1;
} else if (val == 0L) {
return 0;
}
return 1;
}
});
}
return fileList;
}
搜索该异常信息,我发现这是比较器写的不规范导致的。
Java的比较器有所谓的“一致性”原则,大概原理类似于:
因此,书写比较器时需要考虑到所有的情况。
上述的代码出现异常的原因,就是没有考虑lFile或rFile为null的情况。
尽管我能够保证这两个参数不为null,但JVM并不知道,它要求比较器的逻辑必须严谨,
严格考虑各种情况下两者大小的判定原则。
针对这个问题,可以按照下列代码进行修改:
public ArrayList<File> getAllLogFiles(int flag) {
ArrayList<File> fileList = new ArrayList<File>();
//省略获取File的逻辑
........
if (fileList.size() > 1) {
ArrayList<File> tmpList = new ArrayList<File>(fileList);
try {
Collections.sort(tmpList, new Comparator<File>() {
@Override
public int compare(File lFile, File rFile) {
boolean lInValid = (lFile == null || !lFile.exists());
boolean rInValid = (rFile == null || !rFile.exists());
boolean bothInValid = lInValid && rInValid;
if (bothInValid) {
return 0;
}
if (lInValid) {
return 1;
}
if (rInValid) {
return -1;
}
Long lTime = lFile.lastModified();
Long rTime = rFile.lastModified();
return lTime.compareTo(rTime) * (-1);
}
});
fileList = tmpList;
//lastModified may meet ErrnoException and return 0
//which may cause "Comparison method violates its general contract!"
} catch (IllegalArgumentException e) {
.........
//主动捕获了exception
//主要是获取文件修改时间时,有可能导致ErrnoException,使得违反比较器的"一致性"原则
}
}
return fileList;
}