比较方法违反了它的通用契约!仅适用于Java 7。

38

我知道这个问题已经存在一段时间了,查看了之前的所有答案,但仍然不能解决。

'crew'对象表示具有等级和其他项目的机组人员。比较应通过比较'int'值'assigned_rank'进行,如果两个实例中的该值相等,则应根据布尔值'is_trainer'进行区分。

在Java 7之前,这种方法非常有效。但是自从Java 7以后,我一直遇到这个问题:

java.lang.IllegalArgumentException: Comparison method violates its general contract!
at java.util.ComparableTimSort.mergeLo(ComparableTimSort.java:714)
at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:451)
at java.util.ComparableTimSort.mergeCollapse(ComparableTimSort.java:376)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:182)
at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
at java.util.Arrays.sort(Arrays.java:472)
at java.util.Collections.sort(Collections.java:155)
at dormas_flightlog.Query.getCrew(Query.java:714)

这里是源代码,一些潜在的危险部分已经被注释掉了,但它仍然不能工作:

public class crew implements Serializable, Comparable<crew> {

private static final long serialVersionUID = 36L;
private int flightID = 0;
private int assigned_rank = 25;
private boolean is_trainer = false;
...


@Override
public int compareTo(crew him) {

    int myRank = this.getAssigned_rank();
    int hisRank = him.assigned_rank;

    if (this == him) {
        return 0;
    }
    if (myRank > hisRank) {
        return 1;
    }
    if (myRank < hisRank) {
        return -1;
    }
    if (myRank == hisRank) {
//            if (is_trainer && !o.is_trainer) {
//                i = 1;
//            }
//            if (!is_trainer && o.is_trainer) {
//                i = -1;
//            }
//            if (is_trainer && o.is_trainer) {
//                i = 0;
//            }
//            if (!is_trainer && !o.is_trainer) {
//                i = 0;
//            }
        return 0;
    }

    return 0;
}

@Override
public int hashCode() {
    int hash = 7;
    hash = 31 * hash + this.assigned_rank;
    hash = 31 * hash + (this.is_trainer ? 1 : 0);
    return hash;
}

@Override
public boolean equals(Object o) {

    if (this == o) {
        return true;
    }


    int myRank = this.getAssigned_rank();
    int hisRank = 0;

    if (o instanceof crew) {
        crew him = (crew) o;
        hisRank = him.assigned_rank;
    } else {
        return false;
    }

    if (myRank > hisRank) {
        return false;
    }
    if (myRank < hisRank) {
        return false;
    }
    if (myRank == hisRank) {
//            if (is_trainer && !o.is_trainer) {
//                i = 1;
//            }
//            if (!is_trainer && o.is_trainer) {
//                i = -1;
//            }
//            if (is_trainer && o.is_trainer) {
//                i = 0;
//            }
//            if (!is_trainer && !o.is_trainer) {
//                i = 0;
//            }
        return true;
    }

    return false;
}

}

实现equals()只是尝试解决这个问题。无论是否使用equals(),都会出现给定的异常。我看不出compareTo方法如何违反其契约。非常感谢任何帮助......总有一天,这段代码必须与Java 7一起工作,我不知道该怎么办......


你能展示一下你进行排序的代码吗? - WilQu
分配等级(assigned_rank)和getAssigned_rank()之间有什么区别? - Raymond Chen
类似的问题在这里被问到了:https://dev59.com/KGw15IYBdhLWcg3wbLHU - naresh
你是否找到了真正的解决方案(除了像Naresh建议的那样抑制警告)?在我的情况下,我遇到了类似代码的同样问题。我开始认为这是库代码中的一个错误。 - fishinear
1
你的代码看起来不错。你有一些示例对象(最好是整个最小化示例),可以展示这种情况吗? - Paŭlo Ebermann
4个回答

48

请看以下内容:

来自http://www.oracle.com/technetwork/java/javase/compatibility-417013.html#source

领域:API:实用程序 摘要:更新的数组和集合排序行为可能会抛出IllegalArgumentException

描述:java.util.Arrays.sort及间接使用的java.util.Collections.sort使用的排序算法已被替换。新的排序实现如果检测到违反Comparable协议的Comparable,则可能会抛出IllegalArgumentException。以前的实现会默默地忽略这种情况。如果希望恢复以前的行为,则可以使用新的系统属性java.util.Arrays.useLegacyMergeSort来恢复以前的归并排序行为。

不兼容性质:行为

RFE: 6804124

有关更详细的信息,请参阅此处的错误数据库引用。


6
好的,compareTo实现违反了什么契约? - Hans-Peter Störr
@hstoerr 请查看 https://dev59.com/KGw15IYBdhLWcg3wbLHU#6626568 和 https://dev59.com/KGw15IYBdhLWcg3wbLHU#6626470。 - naresh
如果您能提供一个示例,说明OP的代码如何违反Comparable所需的不可反身性、传递性等要求,那将非常有帮助。链接的文档只是说必须存在错误。也许我有点愚钝,在这里我就是看不到它在哪。 - Hans-Peter Störr

10
也许你只是在通过 Collections.sort(...) 进行比较时遇到了 NaN 值,这对我来说是个问题,即使我正确实现了 compare(obj1, obj2) 方法,我仍然会遇到这个异常!请检查一下!

2
那就是我的问题。谢谢! :) - Matthieu

4

9
这不是jdk7的错误,而是你代码的错误。它违反了比较方法所要求的条件。;-) - Hans-Peter Störr
因为没有给出任何解释就提到了 JDK7 中的错误,所以被踩了。 - Archit
1
哈哈,你的评论让我开心地笑了。我也点了反对。useLegacyMegaSort是一种临时解决方案(而不是解决方法),旨在为Java < 7提供向后兼容性。 - MG Developer

0

很遗憾,这些解决方案都不适用于Android。TimSort在Android的ViewGroup中被广泛使用,与addChildrenForAccessibility相关,该函数在Java 7和8下出现。没有用户代码涉及任何比较。

从其他报告来看,这与具有重叠项的RelativeLayout有关,这是常见的情况。例如,一个TextView出现在图像上方,或者两个项目位于同一位置,您只设置一个可见。

https://code.google.com/p/android/issues/detail?id=55933

我还没有找到解决这个问题的方法。您无法在Android Studio或Eclipse中设置-Djava选项(至少我找不到)。强制使用Java 1.6应该可以解决问题,但实际上并没有。似乎亚马逊的新款Fire平板电脑和手机对此问题比其他设备更敏感。

有传言称Java 9将会有一个修复程序,比如一个运行时选项,但是由于已经存在多年的错误,我怀疑它是否会被修复 - 特别是考虑到Oracle和Google之间的敌意。也许这个bug确实很深入Android代码中,应该在那里修复。但是,对于所有现有设备来说,这不是可行的解决方案,因为已经有超过十亿的设备。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接