我的比较器方法如何违反其一般合同?
How does my comparator method violate its general contract?
第三方包
import java.util.Collections;
import java.util.Comparator;
import org.joda.time.DateTime;
我的比较器
public static Comparator<Task> TASK_PRIORITY = new Comparator<Task>() {
public int compare(Task task1, Task task2) {
if (task1 == null && task2 == null) return 0;
if (task1 == null) return +1; //null last
if (task2 == null) return -1; //null last
// Only consider retries after a task is retried 5+ times
if (task1.getRetries() >= 5 || task2.getRetries() >= 5) {
// Primary sort: retry count (ascending)
int retriesCompare = Integer.compare(task1.getRetries(), task2.getRetries());
if (retriesCompare != 0) return retriesCompare;
}
// Secondary sort: creation time (ascending, null first)
int creationCompare = compareTimeNullFirst(task1.getCreationTime(), task2.getCreationTime());
if (creationCompare != 0) return creationCompare;
// Tertiary sort: load time (ascending, null last)
int loadCompare = compareTimeNullLast(task1.getLoadTime(), task2.getLoadTime());
if (loadCompare != 0) return loadCompare;
return 0;
}
};
private static int compareTimeNullLast(DateTime time1, DateTime time2) {
if (time1 == null && time2 == null) return 0;
if (time1 == null) return +1;
if (time2 == null) return -1;
if (time1.isBefore(time2) return -1;
if (time1.isAfter(time2)) return +1;
return 0;
}
private static int compareTimeNullFirst(DateTime time1, DateTime time2) {
if (time1 == null && time2 == null) return 0;
if (time1 == null) return -1;
if (time2 == null) return +1;
if (time1.isBefore(time2) return -1;
if (time1.isAfter(time2)) return +1;
return 0;
}
使用我的比较器
//tasks is a List<Task>
Collections.sort(tasks, TASK_PRIORITY);
我的问题
我有时 Comparison method violates its general contract!
得到 IllegalArgumentException
。我可以持续使用实时数据抛出此异常 运行 足够长的时间,但我不确定如何解决问题的实际原因。
我的问题
我的比较器有什么问题? (具体来说,我违反了合同的哪一部分?)如何在不掩盖异常的情况下修复它?
备注
- 我正在使用 Java 7,如果不进行重大重写就无法升级。
- 我可以通过将
java.util.Arrays.useLegacyMergeSort
设置为 true
来掩盖异常,但这不是理想的解决方案。
- 我尝试创建测试以随机生成数据并验证每个 contract conditions。我无法抛出异常。
- 我尝试删除重试比较周围的条件,但我最终还是得到了异常。
- 这一行抛出异常:
Collections.sort(tasks, TASK_PRIORITY);
让我们从头说起。我对您的代码的阅读是您的 Comparator 的逻辑是合理的。 (我会避免使用 null Task
和 DateTime
值,但这与您的问题无关。)
可能导致此异常的另一件事是 compare
方法给出不一致的结果,因为 Task
objects 正在更改。实际上,看起来(至少)重试次数的改变在语义上是有意义的。如果有另一个线程正在更改 Task
可以影响排序的字段......而当前线程正在排序......那可能 IllegalArgumentException
。
(比较合同的一部分是,当您对 collection 进行排序时,成对排序不会改变。)
然后你这样说:
I use ImmutableSet.copyOf
to copy the list before sorting, and I do that under a read lock in java.util.concurrent.locks.ReadWriteLock
.
复制 collection 不会复制 collection 的元素。它是一个浅拷贝。所以你最终会得到两个包含相同 objects 的 collection。如果另一个线程改变了 objects 中的任何一个(例如,通过增加重试次数),这可能会改变 objects.
的顺序
锁定确保您拥有一致的副本,但这不是问题所在。
解决方法是什么?我能想到一对:
您可以在复制和排序时锁定某些内容以阻止对 collection 和元素 objects 的所有更新。
你可以deep-copy收集;即创建一个包含原始 collection.
元素副本的新 collection
您可以创建 light-weight objects,其中包含与排序相关的 Task
objects 字段的快照;例如
public class Key implements Comparable<Key> {
private int retries;
private DateTime creation;
private DateTime load;
private Task task;
public Key(Task task) {
this.task = task;
this.retries = task.getRetryCount();
...
}
public int compareTo(Key other) {
// compare using retries, creation, load
}
}
这有一个潜在的好处,就是你复制的信息更少,你可以从 Key
objects 的排序 collection 到原来的 Task
objects.
请注意,所有这些备选方案都比您目前正在做的要慢。我认为没有办法避免这种情况。
第三方包
import java.util.Collections;
import java.util.Comparator;
import org.joda.time.DateTime;
我的比较器
public static Comparator<Task> TASK_PRIORITY = new Comparator<Task>() {
public int compare(Task task1, Task task2) {
if (task1 == null && task2 == null) return 0;
if (task1 == null) return +1; //null last
if (task2 == null) return -1; //null last
// Only consider retries after a task is retried 5+ times
if (task1.getRetries() >= 5 || task2.getRetries() >= 5) {
// Primary sort: retry count (ascending)
int retriesCompare = Integer.compare(task1.getRetries(), task2.getRetries());
if (retriesCompare != 0) return retriesCompare;
}
// Secondary sort: creation time (ascending, null first)
int creationCompare = compareTimeNullFirst(task1.getCreationTime(), task2.getCreationTime());
if (creationCompare != 0) return creationCompare;
// Tertiary sort: load time (ascending, null last)
int loadCompare = compareTimeNullLast(task1.getLoadTime(), task2.getLoadTime());
if (loadCompare != 0) return loadCompare;
return 0;
}
};
private static int compareTimeNullLast(DateTime time1, DateTime time2) {
if (time1 == null && time2 == null) return 0;
if (time1 == null) return +1;
if (time2 == null) return -1;
if (time1.isBefore(time2) return -1;
if (time1.isAfter(time2)) return +1;
return 0;
}
private static int compareTimeNullFirst(DateTime time1, DateTime time2) {
if (time1 == null && time2 == null) return 0;
if (time1 == null) return -1;
if (time2 == null) return +1;
if (time1.isBefore(time2) return -1;
if (time1.isAfter(time2)) return +1;
return 0;
}
使用我的比较器
//tasks is a List<Task>
Collections.sort(tasks, TASK_PRIORITY);
我的问题
我有时 Comparison method violates its general contract!
得到 IllegalArgumentException
。我可以持续使用实时数据抛出此异常 运行 足够长的时间,但我不确定如何解决问题的实际原因。
我的问题
我的比较器有什么问题? (具体来说,我违反了合同的哪一部分?)如何在不掩盖异常的情况下修复它?
备注
- 我正在使用 Java 7,如果不进行重大重写就无法升级。
- 我可以通过将
java.util.Arrays.useLegacyMergeSort
设置为true
来掩盖异常,但这不是理想的解决方案。 - 我尝试创建测试以随机生成数据并验证每个 contract conditions。我无法抛出异常。
- 我尝试删除重试比较周围的条件,但我最终还是得到了异常。
- 这一行抛出异常:
Collections.sort(tasks, TASK_PRIORITY);
让我们从头说起。我对您的代码的阅读是您的 Comparator 的逻辑是合理的。 (我会避免使用 null Task
和 DateTime
值,但这与您的问题无关。)
可能导致此异常的另一件事是 compare
方法给出不一致的结果,因为 Task
objects 正在更改。实际上,看起来(至少)重试次数的改变在语义上是有意义的。如果有另一个线程正在更改 Task
可以影响排序的字段......而当前线程正在排序......那可能 IllegalArgumentException
。
(比较合同的一部分是,当您对 collection 进行排序时,成对排序不会改变。)
然后你这样说:
I use
ImmutableSet.copyOf
to copy the list before sorting, and I do that under a read lock injava.util.concurrent.locks.ReadWriteLock
.
复制 collection 不会复制 collection 的元素。它是一个浅拷贝。所以你最终会得到两个包含相同 objects 的 collection。如果另一个线程改变了 objects 中的任何一个(例如,通过增加重试次数),这可能会改变 objects.
的顺序锁定确保您拥有一致的副本,但这不是问题所在。
解决方法是什么?我能想到一对:
您可以在复制和排序时锁定某些内容以阻止对 collection 和元素 objects 的所有更新。
你可以deep-copy收集;即创建一个包含原始 collection.
元素副本的新 collection
您可以创建 light-weight objects,其中包含与排序相关的
Task
objects 字段的快照;例如public class Key implements Comparable<Key> { private int retries; private DateTime creation; private DateTime load; private Task task; public Key(Task task) { this.task = task; this.retries = task.getRetryCount(); ... } public int compareTo(Key other) { // compare using retries, creation, load } }
这有一个潜在的好处,就是你复制的信息更少,你可以从
Key
objects 的排序 collection 到原来的Task
objects.
请注意,所有这些备选方案都比您目前正在做的要慢。我认为没有办法避免这种情况。