为什么我的比较方法有时会抛出 IllegalArgumentException?
Why does my compare methd throw IllegalArgumentException sometimes?
我遇到这个问题有一段时间了,已经搜索了很多 Whosebug 问题但无法解决我的问题。
我之前也问过类似的问题,得到了使用的建议,
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
它没有解决我的问题。我从未在我的任何测试设备上遇到过此异常,但我的一些用户一直在定期报告它。我真的不知道怎么解决。
异常
这是我遇到的异常,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:743)
at java.util.TimSort.mergeAt(TimSort.java:479)
at java.util.TimSort.mergeCollapse(TimSort.java:404)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2023)
at java.util.Collections.sort(Collections.java:1883)
或者有时这样,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:864)
at java.util.TimSort.mergeAt(TimSort.java:481)
at java.util.TimSort.mergeCollapse(TimSort.java:406)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2010)
at java.util.Collections.sort(Collections.java:1883)
我做了什么
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 != null && d2 != null) {
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 != null && l2 != null) {
result = l1.compareTo(l2);
}
}
}
return result;
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 != null && f2 != null) {
result = Long.valueOf(f1.length()).compareTo(Long.valueOf(f2.length()));
}
}
return result;
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
我是这样排序的,
Collections.sort(dirs, FileItemComparator.getComparator(FileItemComparator.NAME_SORT));
问题
我确信具有传递依赖性的比较方法有问题。我已经尝试了很多,但似乎无法修复它。实际上,我从未在我的任何测试设备中遇到过这个问题,但我的用户不断报告它。
我希望这里的任何人都能够抓住问题并帮助我一劳永逸地解决它。
更新代码(感谢@Eran)
我认为最好通过发布完整的更新代码来帮助其他人。它将帮助很多面临同样问题的人。
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 == null) {
if (d2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (d2 == null) {
return -1;
}
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 == null) {
if (f2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (f2 == null) {
return -1;
}
Long l1 = Long.valueOf(f1.length());
Long l2 = Long.valueOf(f2.length());
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
让我们看看您的第一个比较方法:
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
假设您正在比较两个文件项(我们称它们为 o1 和 o2),一个有文件名,另一个没有(即空文件名)。您的方法将 return 0。
现在,如果您将 o2 与文件名不为空的另一个 FileItem (o3) 进行比较,您将再次 return 0。
但是如果比较 o1 和 o3,因为它们都有非空文件名,所以比较 returns -1 或 1(假设文件名不同)。
因此您的比较不一致,因为它不具有传递性。
如果一个元素缺少比较所需的 属性 而另一个元素没有,则您不应该 return 0。您应该决定是 return 1 还是 - 1(取决于,例如,具有空名称的 FileItems 是否应在具有非空名称的 FileItems 之前或之后排序)。
例如:
public int compare(FileItem o1, FileItem o2)
{
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
这是比较器的一个常见错误 - 您没有始终如一地处理 null
。通常的模式如下所示:
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null < not null
if (o1 == null || o2 == null) {
return -1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
已添加
请注意,此实现也是 一个常见错误,因为我允许 compare(null,not_null)
等于 compare(not_null,null)
,这也违反了合同 - 请使用 或类似的内容。
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null != not null
if (o1 == null || o2 == null) {
// Swap these around if you want 'null' at the other end.
return o1 == null ? -1: 1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
// Swap these around if you want 'null' at the other end.
return n1 == null ? -1: 1;
}
return n1.compareTo(n2);
}
我遇到这个问题有一段时间了,已经搜索了很多 Whosebug 问题但无法解决我的问题。
我之前也问过类似的问题,得到了使用的建议,
System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
它没有解决我的问题。我从未在我的任何测试设备上遇到过此异常,但我的一些用户一直在定期报告它。我真的不知道怎么解决。
异常
这是我遇到的异常,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeLo(TimSort.java:743)
at java.util.TimSort.mergeAt(TimSort.java:479)
at java.util.TimSort.mergeCollapse(TimSort.java:404)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2023)
at java.util.Collections.sort(Collections.java:1883)
或者有时这样,
java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.TimSort.mergeHi(TimSort.java:864)
at java.util.TimSort.mergeAt(TimSort.java:481)
at java.util.TimSort.mergeCollapse(TimSort.java:406)
at java.util.TimSort.sort(TimSort.java:210)
at java.util.TimSort.sort(TimSort.java:169)
at java.util.Arrays.sort(Arrays.java:2010)
at java.util.Collections.sort(Collections.java:1883)
我做了什么
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 != null && d2 != null) {
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 != null && l2 != null) {
result = l1.compareTo(l2);
}
}
}
return result;
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 != null && f2 != null) {
result = Long.valueOf(f1.length()).compareTo(Long.valueOf(f2.length()));
}
}
return result;
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
我是这样排序的,
Collections.sort(dirs, FileItemComparator.getComparator(FileItemComparator.NAME_SORT));
问题
我确信具有传递依赖性的比较方法有问题。我已经尝试了很多,但似乎无法修复它。实际上,我从未在我的任何测试设备中遇到过这个问题,但我的用户不断报告它。
我希望这里的任何人都能够抓住问题并帮助我一劳永逸地解决它。
更新代码(感谢@Eran)
我认为最好通过发布完整的更新代码来帮助其他人。它将帮助很多面临同样问题的人。
enum FileItemComparator implements Comparator<FileItem> {
//Using ENUM
NAME_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
},
DATE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String d1 = o1.getFileDate();
String d2 = o2.getFileDate();
if (d1 == null) {
if (d2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (d2 == null) {
return -1;
}
Long l1 = Long.valueOf(d1);
Long l2 = Long.valueOf(d2);
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
},
SIZE_SORT {
public int compare(FileItem o1, FileItem o2) {
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
File f1 = o1.getItem();
File f2 = o2.getItem();
if (f1 == null) {
if (f2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (f2 == null) {
return -1;
}
Long l1 = Long.valueOf(f1.length());
Long l2 = Long.valueOf(f2.length());
if (l1 == null) {
if (l2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (l2 == null) {
return -1;
}
return l1.compareTo(l2);
}
};
public static Comparator<FileItem> descending(final Comparator<FileItem> other) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
return -1 * other.compare(o1, o2);
}
};
}
public static Comparator<FileItem> getComparator(final FileItemComparator... multipleOptions) {
return new Comparator<FileItem>() {
public int compare(FileItem o1, FileItem o2) {
for (FileItemComparator option : multipleOptions) {
int result = option.compare(o1, o2);
if (result != 0) {
return result;
}
}
return 0;
}
};
}
}
让我们看看您的第一个比较方法:
public int compare(FileItem o1, FileItem o2) {
int result = 0;
if (o1 != null && o2 != null) {
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 != null && n2 != null)
result = n1.compareTo(n2);
}
return result;
}
假设您正在比较两个文件项(我们称它们为 o1 和 o2),一个有文件名,另一个没有(即空文件名)。您的方法将 return 0。
现在,如果您将 o2 与文件名不为空的另一个 FileItem (o3) 进行比较,您将再次 return 0。
但是如果比较 o1 和 o3,因为它们都有非空文件名,所以比较 returns -1 或 1(假设文件名不同)。
因此您的比较不一致,因为它不具有传递性。
如果一个元素缺少比较所需的 属性 而另一个元素没有,则您不应该 return 0。您应该决定是 return 1 还是 - 1(取决于,例如,具有空名称的 FileItems 是否应在具有非空名称的 FileItems 之前或之后排序)。
例如:
public int compare(FileItem o1, FileItem o2)
{
if (o1 == null) {
if (o2 == null) {
return 0;
} else {
return 1; // this will put null in the end
}
} else if (o2 == null) {
return -1;
}
String n1 = o1.getFileName();
String n2 = o2.getFileName();
if (n1 == null) {
if (n2 == null) {
return 0;
} else {
return 1; // this will put null names after non null names
}
} else if (n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
这是比较器的一个常见错误 - 您没有始终如一地处理 null
。通常的模式如下所示:
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null < not null
if (o1 == null || o2 == null) {
return -1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
return -1;
}
return n1.compareTo(n2);
}
已添加
请注意,此实现也是 一个常见错误,因为我允许 compare(null,not_null)
等于 compare(not_null,null)
,这也违反了合同 - 请使用
public int compare(FileItem o1, FileItem o2) {
// null == null
if (o1 == null && o2 == null) {
return 0;
}
// null != not null
if (o1 == null || o2 == null) {
// Swap these around if you want 'null' at the other end.
return o1 == null ? -1: 1;
}
// Neither can be null now so this is safe.
String n1 = o1.getFileName();
String n2 = o2.getFileName();
// Same logic again.
if (n1 == null && n2 == null) {
return 0;
}
if (n1 == null || n2 == null) {
// Swap these around if you want 'null' at the other end.
return n1 == null ? -1: 1;
}
return n1.compareTo(n2);
}