6
java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.TimSort.mergeLo(TimSort.java:747)
    at java.util.TimSort.mergeAt(TimSort.java:483)
    at java.util.TimSort.mergeCollapse(TimSort.java:410)
    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 static Comparator<MyClass> CMP_TIME_DESC = new Comparator<MyClass>() {
    @Override
    public int compare(MyClass o1, MyClass o2) {
        return o2.getOrderSendTime().compareTo(o1.getOrderSendTime());
    }
};

这些值始终为非空值。getOrderSendTime() 对象属于 java.util.Date 类。

我知道这是传递性不一致,我会假设这样的课程不会有这样的问题。我搜索了未解决的问题,但没有找到任何关于该主题的问题。

有任何想法吗?

4

4 回答 4

4

您的问题与此有关:Java 7 中的排序算法更改

发生这种情况是因为默认排序算法已从 MergeSort 更改为 TimSort

一种解决方法是添加-Djava.util.Arrays.useLegacyMergeSort=true到 JVM 环境中。

最好的选择是遵守比较通用合同,但我认为您在问题中没有为此提供足够的信息。

于 2015-12-02T17:13:23.493 回答
3

我有同样的异常,当我在同一个列表/数组中排序时,它发生在 Java8 上运行的对象java.util.Datejava.sql.Timestamp(这种混合是由于一些对象是从具有该Timestamp数据类型的数据库记录中加载的,而另一些对象是手动创建的,并且这些对象中只有一个Date对象。)

每次对相同的数据集进行排序时,也不会发生异常,而且似乎数组中还必须至少有 32 个这些混合对象才能发生。

如果我使用传统的排序算法,这也不会发生(请参阅 Ortomala Lokni 的答案)。

java.util.Date如果您仅使用对象或仅使用java.sql.Timestamp数组中的对象,这也不会发生。

因此,该问题似乎TimSort与 和 中的 compareTo 方法相java.util.Date结合java.sql.Timestamp

但是,由于它已在 Java 9 中修复,因此研究为什么会发生这种情况并没有让我付出任何代价!

作为一种解决方法,直到 Java9 发布并且我们可以更新我们的系统,我们手动实现了一个Comparator仅使用getTime(). 这似乎工作正常。

以下是可用于重现该问题的代码:

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.junit.Test;

public class TimSortDateAndTimestampTest {

    // the same test data with all Dates, all Timestamps, all Strings or all Longs does NOT fail.
    // only fails with mixed Timestamp and Date objects
    @Test
    public void testSortWithTimestampsAndDatesFails() throws Exception {
        List<Date> dates = new ArrayList<>();
        dates.add(new Timestamp(1498621254602L));
        dates.add(new Timestamp(1498621254603L));
        dates.add(new Timestamp(1498621254603L));
        dates.add(new Timestamp(1498621254604L));
        dates.add(new Timestamp(1498621254604L));
        dates.add(new Timestamp(1498621254605L));
        dates.add(new Timestamp(1498621254605L));
        dates.add(new Timestamp(1498621254605L));
        dates.add(new Timestamp(1498621254605L));
        dates.add(new Timestamp(1498621254606L));
        dates.add(new Timestamp(1498621254607L));
        dates.add(new Date(1498621254605L));
        dates.add(new Timestamp(1498621254607L));
        dates.add(new Timestamp(1498621254609L));
        dates.add(new Date(1498621254603L));
        dates.add(new Date(1498621254604L));
        dates.add(new Date(1498621254605L));
        dates.add(new Date(1498621254605L));
        dates.add(new Date(1498621254607L));
        dates.add(new Timestamp(1498621254607L));
        dates.add(new Date(1498621254608L));
        dates.add(new Timestamp(1498621254608L));
        dates.add(new Date(1498621254611L));
        dates.add(new Timestamp(1498621254612L));
        dates.add(new Timestamp(1498621254613L));
        dates.add(new Date(1498621254607L));
        dates.add(new Timestamp(1498621254607L));
        dates.add(new Timestamp(1498621254608L));
        dates.add(new Timestamp(1498621254609L));
        dates.add(new Timestamp(1498621254611L));
        dates.add(new Date(1498621254603L));
        dates.add(new Date(1498621254606L));

        for (int i = 0; i < 200; i++) {
            Collections.shuffle(dates);
            Collections.sort(dates);
        }
    }
}

编辑:我已经删除了异常期望,所以你可以看到它在运行时抛出。

于 2017-06-29T17:41:45.583 回答
0

将“-Djava.util.Arrays.useLegacyMergeSort=true”添加到 VM 参数。

于 2017-09-05T03:10:02.523 回答
0

今天遇到这个问题,花了几个小时,才发现自己是在比较Longs。相反,您需要比较 Long.longValue()。

MyData.utcTime() 返回 Long。所以而不是:

public static Comparator<MyData> sortByUtcPublishedDesc = new Comparator<MyData>() {
    @Override
    public int compare(MyData n1, MyData n2) {
        if ( n2.utcTime() < n1.utcTime() )
            return -1;
        if ( n2.utcTime() == n1.utcTime() )
            return 0;
        // if (n2.utcTime() > n1.utcTime())
        return 1;
    }
};

我用了,下面来解决这个问题。

public static Comparator<MyData> sortByUtcPublishedDesc = new Comparator<MyData>() {
    @Override
    public int compare(MyData n1, MyData n2) {
        if ( n2.utcTime().longValue() < n1.utcTime().longValue() )
            return -1;
        if ( n2.utcTime().longValue() == n1.utcTime().longValue() )
            return 0;
        // if (n2.utcTime() > n1.utcTime())
        return 1;
    }
};
于 2020-09-13T09:13:47.410 回答