"비교 방법이 일반 계약을 위반합니다!"


187

누군가가 간단한 용어로 설명 할 수 있습니까? 왜이 코드에서 "비교 방법이 일반 계약을 위반합니다!"라는 예외가 발생합니까?

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

1
예외의 이름과 등급은 무엇입니까? IllegalArgumentException입니까? 내가 추측해야한다면 나는 s1.getParent().equals(s2)대신에 해야한다고 생각합니다 s1.getParent() == s2.
Freiheit

그리고 예외도 발생합니다.
Matthew Farwell

2
Java 또는 Java 비교 API에 대해서는 잘 모르지만이 비교 방법은 잘못되었습니다. 한다고 가정은 s1의 부모 s2s2의 부모 아닙니다 s1. 그 다음 compareParents(s1, s2)이다 0, 그러나 compareParents(s2, s1)입니다 1. 말이되지 않습니다. (또한, 아래 언급 된 aix와 같이 전 이적이지 않습니다.)
mqp

4
이 오류는 특정 라이브러리에 의해 생산 될 것으로 보인다 cr.openjdk.java.net/~martin/webrevs/openjdk7/timsort/src/share/...
피터 Lawrey에게

Java에서는 equals (부울 리턴) 또는 compareTo (-1, 0 또는 +1 리턴)를 사용할 수 있습니다. 당신의 푸 클래스에서이 기능을 무시하고 그 후, 당신은 s1.getParent ()을 확인할 수 있습니다 (S2)과 동일 ....
Mualig

답변:


261

당신의 비교기는 전 이적이지 않습니다.

하자 A의 부모 BB의 부모 C. 때문에 A > B그리고 B > C, 그것은 그 사건해야합니다 A > C. 그러나 비교기가 AC에서 호출되면 0을 의미 A == C합니다. 이는 계약을 위반하므로 예외가 발생합니다.

라이브러리가 이상하게 행동하기보다는 이것을 감지하고 알려주는 것이 좋습니다.

과도 성 요건을 충족시키는 한 가지 방법 은 직계 조상 만 보는 대신 체인 compareParents()을 통과하는 getParent()것입니다.


3
자바 7의에 도입 java.util.Arrays.sort stackoverflow.com/questions/7849539/...
leonbloy

46
도서관이 이것을 감지한다는 사실은 훌륭합니다. 태양의 누군가가 당신을 환영 합니다.
Qix-MONICA가 MISTREATED했습니다.

이 질문을 참조 게시물로 더 유용하게 만들기 위해이 답변을 약간 일반화 할 수 있습니까?
Bernhard Barker

1
@Qix-Sun을 좋아하는만큼 Oracle 배너 아래 Java 7에 추가되었습니다
isapir

1
@isapir 젠장! 잘 잡았습니다.
Qix-MONICA가 MISTREATED했습니다.

38

이것이 내가이 오류를 Googled했을 때 얻은 것이므로 내 문제는

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

value >= other.value(분명히) 실제로해야 value > other.value당신이 실제로 동일한 개체를 0을 반환 할 수 있도록.


7
난 당신의 어떤 경우에 것을 추가해야합니다 value(경우 NaN이 인 valuedoublefloat), 그것뿐만 아니라 실패합니다.
Matthieu

22

계약 위반은 종종 비교자가 객체를 비교할 때 정확하거나 일관된 값을 제공하지 않음을 의미합니다. 예를 들어, 문자열 비교를 수행하고 빈 문자열을 다음과 같이 끝까지 정렬하도록 할 수 있습니다.

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과 2가 모두 비어있는 경우를 간과합니다.이 경우 잘못된 값이 반환되고 (0이 아닌 1이 일치 함) 비교기가이를 위반으로보고합니다. 다음과 같이 작성되어야합니다.

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 );

13

귀하의 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;
}   

2
도움이되었습니다! 내 코드는 논리적으로 괜찮 았지만 정밀도 문제로 인해 오류가 발생했습니다.
JSong

6

우리의 경우 실수로 s1과 s2의 비교 순서를 뒤집었기 때문에이 오류가 발생했습니다. 그러니 조심하십시오. 분명히 다음보다 훨씬 복잡했지만 이것은 예시입니다.

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

3

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 metohod를 호출하기 만하면됩니다.
Martin

코드가 컴파일되지 않습니다. 클래스 Compare, Convert(잠재적으로 다른 사람이) 정의되지 않습니다. 독립적 인 예제로 코드 스 니펫을 업데이트하십시오.
길리

코드를 더 읽기 쉽도록 오타를 수정하고 checkConsi(s)tency모든 중복 @param선언을 제거 해야합니다 .
Roland Illig

3

제 경우에는 다음과 같은 일을하고있었습니다.

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가 모두 null 일 때였습니다.


3

나는 종종 null 값에 대한 반복 검사가 수행되는 코드 조각에서 이런 일이 발생하는 것을 보았습니다.

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

2

경우 compareParents(s1, s2) == -1다음이 compareParents(s2, s1) == 1기대된다. 코드를 사용하면 항상 사실이 아닙니다.

특히 s1.getParent() == s2 && s2.getParent() == s1. 가능한 문제 중 하나 일뿐입니다.


1

VM 구성 편집이 도움이되었습니다.

-Djava.util.Arrays.useLegacyMergeSort=true

서식 지정에 도움을 주려고 시도해도 아무런 문제가 없는지 다시 확인하십시오. -제안 된 솔루션의 시작에 대해 확신이 없습니다 . 아마도 당신은 하나의 항목 글 머리 기호 목록과 같은 것을 의도했을 것입니다.
Yunnosch

2
또한 이것이 설명 된 문제에 어떻게 도움이되는지 설명하십시오. 현재 코드 전용 답변입니다.
Yunnosch

0

다음과 같이 객체 데이터를 비교할 수 없습니다 s1.getParent() == s2.-객체 참조를 비교합니다. equals functionFoo 클래스를 재정의 한 다음 다음과 같이 비교해야합니다s1.getParent().equals(s2)


아니요, 실제로 OP는 일종의 목록을 정렬하려고 시도하고 실제로 참조를 비교하려고합니다.
Edward Falk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.