当前位置: 首页 > 知识库问答 >
问题:

只有在某些情况下才会抛出“比较方法违反其一般约定”

齐元章
2023-03-14

首先,我知道这个问题在许多其他线程中都有描述。但是我无法找到并回答这个问题,为什么这个错误并不总是被抛出?

让我描述一下我的意思。我写了一些示例代码来说明这一点:

public class Mushroom {

    public int size;

    public Mushroom(int size) {
        this.size = size;
    }

    @Override
    public boolean equals(Object obj) {
        //this is intentionally false - read in description
        return false;
    }
}

DSA

public class MushroomComparator implements Comparator<Mushroom> {

    @Override
    public int compare(Mushroom o1, Mushroom o2) {
        // here is the code which breaks the contract
         if (o1.size < o2.size){
             return 1;
         }else if(o1.size >o2.size){
             return -1;
         }
         return 1;
    }

}

最后进行比较测试

public class ComparisonTest {
    public static void main(String[] args) {
//      System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");
    List<Mushroom> forest = new ArrayList<>();

        for(int i =0; i<18; i++){
            Mushroom mushroom1 = new Mushroom(1);
            Mushroom mushroom2 = new Mushroom(3);
            Mushroom mushroom3 = new Mushroom(2);

            forest.add(mushroom1);
            forest.add(mushroom2);
            forest.add(mushroom3);

        }
        Collections.sort(forest, new MushroomComparator());
    }
}

在运行时,我们会收到这个描述的问题

Java . lang . illegalargumentexception:比较法违反了它的通用契约!

根据托收文件。排序方法:

(可选)如果实现检测到列表元素的自然排序违反了可比协定

因此,让我们回答这个问题,这个合同在 Comparable 文档中是什么(在我的示例中,我使用 Comparator,但从文档中它应该满足相同的要求)

实现者必须确保所有 x 和 y 的 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))

我故意打破这个规则来得到我所写的错误。这是compareTo方法的实现应该遵循的规则之一。其余的在文档中有描述,但是一般来说,我理解文档等价关系应该被满足

从比较契约中可以立即得出,商是C上的等价关系,自然排序是C上一个全序

现在真正困扰我的是我的测试方法中迭代数的变化。如果你把数字从18改为22,那么…异常将不会被抛出。这个异常被描述为可选的,所以它确实意味着有时这个异常会被抛出,有时不会。我没有深入研究排序算法(TimSort)的新实现(自Java7以来排序算法的变化)。我知道检查每组数据是否违反比较器契约可能会消耗一些CPU,但是什么是连接有时显示这一点,有时没有。这可能真的是误导。

此外,我可以将compare方法的实现更改为简单的..return 1。根据文档,它应该违反合同。但事实并非如此。在文档中还保留了一些关于合同的平等方法,但实际上并不需要

强烈建议但并非严格要求(x.compareTo(y)==0)==(x.equals(y))

为了在我的示例中检查它,我实现了equals方法以返回始终为假。有了这个,我确信equals方法不会对破坏比较器契约施加影响。

现在我的问题是:什么是真正的比较器契约(在破坏它的背景下)?为什么对于某些数据集,这个异常被抛出,而对于其他数据集,这个异常没有被抛出?也许我错过了什么?最后但同样重要的是——抛出这个异常需要打破哪些规则?

需要注意的是,这个问题的解决方案可能是关闭排序算法的这个新实现,这里描述了这个算法,并在我的示例代码中进行了注释。

共有2个答案

农存
2023-03-14

事实上,如果<code>Collections,则总是抛出异常。sort()有机会检测比较器的错误行为。这种情况发生在某些罕见的情况下,sort()函数知道某个项目应该落在某个范围内,因为比较器之前已经指出了这一点,现在它正在调用比较器,并且被告知该项目应该落在此范围之外。但是sort()非常复杂,它试图尽可能少地工作,因此检测这种错误行为的条件通常不会发生。

如果< code>sort()总是抛出异常,那么在开始对你的列表进行排序之前,它必须先发制人地调用你的比较器n^2(n的平方)次,以确保它总是对你的列表中的条目对的所有排列遵守它的约定。这将是极其低效的。

对比方必须遵守的合同见文件:(https://docs.oracle.com/javase/7/docs/api/java/util/Comparator.html)

>

  • 实现者必须确保所有x和y的sgn(compare(x,y))==-sgn(比较(y,x))

    实现者还必须确保关系是可传递的:<code>((compare(x,y)

    最后,实现者必须确保compare(x,y)==0意味着sgn(compare,x))==sgn。

    (其中 sgn(x) 是 'Math.signum()' 函数。

  • 吕胤
    2023-03-14

    为什么并不总是抛出此错误

    因为验证您的比较方法不是 Collections.sort 的工作。它的工作只是实现排序。但是这样做的逻辑可以揭示无效的比较方法,作为其逻辑在某些条件分支中的副产品。在这种情况下,抛出而不是尝试继续使用无效的比较方法进行排序是有意义的。

    什么是真正的比较者合同(在违反合同的情况下)?

    如果你违反了合同,排序可能无法正常工作,或者根本无法正常工作。(并不是说它会验证您的比较方法。

    为什么对于某些数据集会抛出此异常,而对于其他数据集则不会?

    如果排序没有碰巧遵循导致排序代码可以检测到的逻辑缺陷的路径,则它不会知道抛出。但是正在使用的 TimSort 确实碰巧进入了一个逻辑分支,该分支揭示了无效的比较方法,因此它确实抛出了。

     类似资料:
    • 可能的重复: 为什么我的比较方法会抛出异常 — 比较方法违反了其一般合同! 我有这个代码: 有时它会引发以下异常: 为什么? 1) 我该如何避免呢?2) 我怎么能抓住这个例外? 提前谢谢。

    • 我在Java代码中没有使用任何Comparator/Sorting,它仍然抛出“Java.lang.IllegalArgumentException:Comparison方法违反了它的一般约定!”例外 下面是调试时在restTemplate.exchange行抛出异常的一段代码。 当我将springbootstarter父版本从2.3.9更改为2.5.3时,开始出现此异常 我应该如何解决这个问题?

    • 以下几行: 返回以下异常:比较方法违反了其一般约定! 我知道这个异常通常是在没有正确实现比较方法时产生的,但是在我的例子中,它的实现是相当明显的: 正如您所看到的,目标是按照值的hashValue属性对值进行排序。 任何关于我做错了什么的想法/提示将不胜感激! 谢谢托马斯

    • 我想通过dateLastContact比较两个“收件人”,如果相同,就通过地址进行比较。这是我的代码: 而且我总是有这个错误: 我尝试了很多方法,但是现在,我不知道该怎么办。你能帮我吗? 收件人类别:

    • 问题内容: 我看到了很多与此有关的问题,并试图解决该问题,但是经过一个小时的搜索和大量的试验和错误后,我仍然无法修复它。我希望你们中的一些人能抓住问题。 这是我得到的: 这是我的比较器: 任何想法? 问题答案: 异常消息实际上是描述性的。这里所指的合同是传递:如果和那么对于任意的。我用纸和铅笔检查了一下,你的代码似乎有几个孔: 如果你不返回。 如果id不相等,则返回。你应该返回-1或1根据哪个ID

    • 我收到以下错误: 比较方法违反了其总合同! 这是我的比较法 我想比较项目的分数。但是当分数相同时,我想按名称对它们进行排序。 我需要更改什么,为什么会出现这个错误? 编辑: 分数是一个,itemname一个。 这是 ComparableItem 类: 这是MenuList项目类: