【问题标题】:"Comparison method violates its general contract!"“比较法违反了它的总契约!”
【发布时间】:2022-01-04 14:31:42
【问题描述】:

谁能简单解释一下,为什么这段代码会抛出异常,“比较方法违反了它的一般约定!”,我该如何解决?

private int compareParents(Foo s1, Foo s2) {
    if (s1.getParent() == s2) return -1;
    if (s2.getParent() == s1) return 1;
    return 0;
}

【问题讨论】:

  • 异常的名称和类别是什么?它是 IllegalArgumentException 吗?如果我不得不猜测,我会认为您应该使用s1.getParent().equals(s2) 而不是s1.getParent() == s2
  • 还有抛出的异常。
  • 我对 Java 或 Java 比较 API 了解不多,但这种比较方法似乎大错特错。假设s1s2 的父级,而s2 不是s1 的父级。那么compareParents(s1, s2)0,但compareParents(s2, s1)1。那没有意义。 (此外,它不是传递的,就像下面提到的 aix。)
  • 在 java 中,您可以使用 equals(返回布尔值)或 compareTo(返回 -1、0 或 +1)。在您的 Foo 类中覆盖此函数,然后,您可以检查 s1.getParent().equals(s2) ...

标签: java comparator


【解决方案1】:

您的比较器不具有传递性。

AB 的父级,BC 的父级。既然A > BB > C,那么一定是A > C。但是,如果在AC 上调用比较器,它将返回零,即A == C。这违反了合同,因此引发了异常。

图书馆很高兴能检测到这一点并让你知道,而不是行为不规律。

满足compareParents() 中传递性要求的一种方法是遍历getParent() 链,而不是只查看直接祖先。

【讨论】:

  • 在 Java 7 中引入 java.util.Arrays.sort stackoverflow.com/questions/7849539/…
  • 图书馆检测到这一点的事实非常棒。有人在太阳应该扔出一个巨大的不客气
  • 你能概括一下这个答案,让这个问题作为参考帖子更有用吗?
  • @Qix - 尽管我很喜欢 Sun,但它是在 Java 7 中添加到 Oracle 旗帜下的
  • @isapir 该死!很好的收获。
【解决方案2】:

您不能像这样比较对象数据:s1.getParent() == s2 - 这将比较对象引用。您应该为 Foo 类覆盖equals function,然后像这样比较它们s1.getParent().equals(s2)

【讨论】:

  • 不,实际上我认为 OP 正在尝试对某种列表进行排序,并且想要实际比较参考。
【解决方案3】:

只是因为这是我在谷歌上搜索这个错误时得到的,我的问题是我有

if (value < other.value)
  return -1;
else if (value >= other.value)
  return 1;
else
  return 0;

value &gt;= other.value 应该(显然)实际上是 value &gt; other.value 以便您实际上可以返回具有相等对象的 0。

【讨论】:

  • 我必须补充一点,如果您的任何 value 是 NaN(如果 valuedoublefloat),它也会失败。
【解决方案4】:

我在一段代码中看到了这种情况,其中经常重复检查空值:

if(( A==null ) && ( B==null )
  return +1;//WRONG: two null values should return 0!!!

【讨论】:

    【解决方案5】:

    违反合同通常意味着比较器在比较对象时未提供正确或一致的值。例如,您可能希望执行字符串比较并强制空字符串排序到末尾:

    if ( one.length() == 0 ) {
        return 1;                   // empty string sorts last
    }
    if ( two.length() == 0 ) {
        return -1;                  // empty string sorts last                  
    }
    return one.compareToIgnoreCase( two );
    

    但这忽略了第一个和两个都为空的情况 - 在这种情况下,返回错误的值(1 而不是 0 表示匹配),并且比较器将其报告为违规。应该写成:

    if ( one.length() == 0 ) {
        if ( two.length() == 0 ) {
            return 0;               // BOth empty - so indicate
        }
        return 1;                   // empty string sorts last
    }
    if ( two.length() == 0 ) {
        return -1;                  // empty string sorts last                  
    }
    return one.compareToIgnoreCase( two );
    

    【讨论】:

      【解决方案6】:

      Java 不检查严格意义上的一致性,只会在遇到严重问题时通知您。它也没有从错误中为您提供太多信息。

      我对分拣机中发生的事情感到困惑,并做了一个严格的一致性检查器,也许这会对你有所帮助:

      /**
       * @param dailyReports
       * @param comparator
       */
      public static <T> void checkConsitency(final List<T> dailyReports, final Comparator<T> comparator) {
        final Map<T, List<T>> objectMapSmallerOnes = new HashMap<T, List<T>>();
      
        iterateDistinctPairs(dailyReports.iterator(), new IPairIteratorCallback<T>() {
          /**
           * @param o1
           * @param o2
           */
          @Override
          public void pair(T o1, T o2) {
            final int diff = comparator.compare(o1, o2);
            if (diff < Compare.EQUAL) {
              checkConsistency(objectMapSmallerOnes, o1, o2);
              getListSafely(objectMapSmallerOnes, o2).add(o1);
            } else if (Compare.EQUAL < diff) {
              checkConsistency(objectMapSmallerOnes, o2, o1);
              getListSafely(objectMapSmallerOnes, o1).add(o2);
            } else {
              throw new IllegalStateException("Equals not expected?");
            }
          }
        });
      }
      
      /**
       * @param objectMapSmallerOnes
       * @param o1
       * @param o2
       */
      static <T> void checkConsistency(final Map<T, List<T>> objectMapSmallerOnes, T o1, T o2) {
        final List<T> smallerThan = objectMapSmallerOnes.get(o1);
      
        if (smallerThan != null) {
          for (final T o : smallerThan) {
            if (o == o2) {
              throw new IllegalStateException(o2 + "  cannot be smaller than " + o1 + " if it's supposed to be vice versa.");
            }
            checkConsistency(objectMapSmallerOnes, o, o2);
          }
        }
      }
      
      /**
       * @param keyMapValues 
       * @param key 
       * @param <Key> 
       * @param <Value> 
       * @return List<Value>
       */ 
      public static <Key, Value> List<Value> getListSafely(Map<Key, List<Value>> keyMapValues, Key key) {
        List<Value> values = keyMapValues.get(key);
      
        if (values == null) {
          keyMapValues.put(key, values = new LinkedList<Value>());
        }
      
        return values;
      }
      
      /**
       * @author Oku
       *
       * @param <T>
       */
      public interface IPairIteratorCallback<T> {
        /**
         * @param o1
         * @param o2
         */
        void pair(T o1, T o2);
      }
      
      /**
       * 
       * Iterates through each distinct unordered pair formed by the elements of a given iterator
       *
       * @param it
       * @param callback
       */
      public static <T> void iterateDistinctPairs(final Iterator<T> it, IPairIteratorCallback<T> callback) {
        List<T> list = Convert.toMinimumArrayList(new Iterable<T>() {
      
          @Override
          public Iterator<T> iterator() {
            return it;
          }
      
        });
      
        for (int outerIndex = 0; outerIndex < list.size() - 1; outerIndex++) {
          for (int innerIndex = outerIndex + 1; innerIndex < list.size(); innerIndex++) {
            callback.pair(list.get(outerIndex), list.get(innerIndex));
          }
        }
      }
      

      【讨论】:

      • 只需使用参数列表和比较器调用 checkConsitency 方法。
      • 您的代码无法编译。未定义类 CompareConvert(以及可能的其他类)。请使用独立的示例更新代码片段。
      • 您应该修正checkConsi(s)tency 中的错字并删除所有多余的@param 声明以使代码更具可读性。
      【解决方案7】:

      在我们的例子中出现这个错误是因为我们不小心颠倒了 s1 和 s2 的比较顺序。所以要注意这一点。它显然比下面的要复杂得多,但这是一个例子:

      s1 == s2   
          return 0;
      s2 > s1 
          return 1;
      s1 < s2 
          return -1;
      

      【讨论】:

        【解决方案8】:

        即使您的 compareTo 在理论上具有传递性,有时细微的错误也会使事情变得混乱……例如浮点算术错误。它发生在我身上。这是我的代码:

        public int compareTo(tfidfContainer compareTfidf) {
            //descending order
            if (this.tfidf > compareTfidf.tfidf)
                return -1;
            else if (this.tfidf < compareTfidf.tfidf)
                return 1;
            else
                return 0;
        
        }   
        

        传递属性显然成立,但由于某种原因,我得到了 IllegalArgumentException。事实证明,由于浮点运算中的微小误差,导致传递属性在不应该出现的地方中断的舍入误差!所以我重写了代码以考虑真正微小的差异 0,它起作用了:

        public int compareTo(tfidfContainer compareTfidf) {
            //descending order
            if ((this.tfidf - compareTfidf.tfidf) < .000000001)
                return 0;
            if (this.tfidf > compareTfidf.tfidf)
                return -1;
            else if (this.tfidf < compareTfidf.tfidf)
                return 1;
            return 0;
        }   
        

        【讨论】:

        • 这很有帮助!我的代码逻辑上没问题,但由于精度问题出现了错误。
        【解决方案9】:

        就我而言,我正在做类似以下的事情:

        if (a.someField == null) {
            return 1;
        }
        
        if (b.someField == null) {
            return -1;
        }
        
        if (a.someField.equals(b.someField)) {
            return a.someOtherField.compareTo(b.someOtherField);
        }
        
        return a.someField.compareTo(b.someField);
        

        我忘记检查的是 a.someField 和 b.someField 何时都为空。

        【讨论】:

          【解决方案10】:

          如果 compareParents(s1, s2) == -1 则应为 compareParents(s2, s1) == 1。使用您的代码并不总是正确的。

          特别是s1.getParent() == s2 &amp;&amp; s2.getParent() == s1。 这只是可能的问题之一。

          【讨论】:

            【解决方案11】:

            编辑虚拟机配置对我有用。

            -Djava.util.Arrays.useLegacyMergeSort=true
            

            【讨论】:

            • 请仔细检查我帮助您进行格式化的尝试并没有破坏任何东西。我不确定- 是建议解决方案的开始。也许您打算改用单项项目符号列表之类的东西。
            • 另外请解释这如何帮助解决所描述的问题。目前它实际上是一个纯代码的答案。
            【解决方案12】:

            在我的例子中,这是一个无限的排序。 即,首先该行根据条件向上移动,然后同一行向下移动到同一位置。 我在最后添加了一个条件,明确地确定了行的顺序。

            【讨论】:

              【解决方案13】:

              我遇到了同样的问题,我解决了。

              //This this your code
              
              private int compareParents(Foo s1, Foo s2) {
                  if (s1.getParent() == s2) return -1;
                  if (s2.getParent() == s1) return 1;
                  return 0;
              }
              

              违规行为是将不同的事物相互比较。

              //acceptable
              compare between s1.getParent() and s2.getParent()
              
              //acceptable
              compare between s1 and s2
              
              //NOT acceptable
              compare between s1 and s2.getParent()
              
              //NOT acceptable
              compare between s1.getParent() and s2
              

              在我的代码中,我想通过地址的协调对地址进行排序。在比较器中,我比较的是 X 和 Y(错误地),而不是 X 和 X。

              //My code:
                  private void sortBasedOnX(){
                      //addresses is a list of addresses where each address has X and Y
                      addresses.sort((o1, o2) -> {
              
                          String a = o1.getAddress().getX(); 
                          String b = o2.getAddress().getY(); //<-- this is supposed to be getX
              
                          return Integer.parseInt(a)-Integer.parseInt(b);
                      });
                  }
              
              //acceptable
              compare between o1.getAddress().getX() and o1.getAddress().getX()
              
              //acceptable
              compare between o1.getAddress().getY() and o1.getAddress().getY()
              
              //NOT acceptable
              compare between o1.getAddress().getX() and o1.getAddress().getY()
              
              //NOT acceptable
              compare between o1.getAddress().getX() and o1.getAddress()
              
              //NOT acceptable
              compare between o1.getAddress().getX() and o1
              

              【讨论】:

                猜你喜欢
                • 2018-03-05
                • 1970-01-01
                • 2012-10-24
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多