Java 오류 : 비교 방법이 일반 계약을 위반 함


84

이것에 대한 많은 질문을보고 문제를 해결하려고 노력했지만 한 시간 동안 인터넷 검색과 많은 시행 착오 끝에 여전히 고칠 수 없습니다. 여러분 중 일부가 문제를 파악하기를 바랍니다.

이것이 내가 얻는 것입니다.

java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.util.ComparableTimSort.mergeHi(ComparableTimSort.java:835)
    at java.util.ComparableTimSort.mergeAt(ComparableTimSort.java:453)
    at java.util.ComparableTimSort.mergeForceCollapse(ComparableTimSort.java:392)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:191)
    at java.util.ComparableTimSort.sort(ComparableTimSort.java:146)
    at java.util.Arrays.sort(Arrays.java:472)
    at java.util.Collections.sort(Collections.java:155)
    ...

그리고 이것은 내 비교기입니다.

@Override
public int compareTo(Object o) {
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());

    if (card1.getSet() < card2.getSet()) {
        return -1;
    } else {
        if (card1.getSet() == card2.getSet()) {
            if (card1.getRarity() < card2.getRarity()) {
                return 1;
            } else {
                if (card1.getId() == card2.getId()) {
                    if (cardType > item.getCardType()) {
                        return 1;
                    } else {
                        if (cardType == item.getCardType()) {
                            return 0;
                        }
                        return -1;
                    }
                }
                return -1;
            }
        }
        return 1;
    }
}

어떤 생각?


이 예외가 발생하는 코드 줄은 무엇입니까? ComparableTimSort.java의 835 및 453 행에는 무엇이 있습니까?
뱀장어의 호버 전체

이 메서드를 Card클래스에 위임하고 return card1.compareTo(card2)거기에서이 논리를 구현 해야하는 것 같습니다 .
Hunter McMillen 2012

2
@HovercraftFullOfEels 나에 의해 작성되지 않은 Oracle의 클래스입니다. 그 라인에서 예외입니다. 이 방법은 매우 길고 이해하기 어려워 보입니다.
Lakatos Gyula 2012

@HunterMcMillen 나는 그것을 할 수 없습니다. 카드에는 카드의 정적 데이터 (이름, 텍스트 등) 만 포함되며이 클래스에는 유형 및 금액과 같은 일부 변경 변수가 포함됩니다.
Lakatos Gyula

1
Clean Code 책을 읽은 후 나도 모른다.
Lakatos Gyula 2014 년

답변:


97

예외 메시지는 실제로 매우 설명 적입니다. 그것이 언급하는 계약은 전이성입니다 : if A > Band B > Cthen for any A, Band C: A > C. 종이와 연필로 확인했는데 코드에 구멍이 거의없는 것 같습니다.

if (card1.getRarity() < card2.getRarity()) {
  return 1;

-1이면 반환하지 않습니다 card1.getRarity() > card2.getRarity().


if (card1.getId() == card2.getId()) {
  //...
}
return -1;

-1ID가 같지 않으면 반환 합니다. 당신은 반환해야 -1또는 1에 따라 ID가 더 크다.


이것 좀보세요. 훨씬 더 읽기 쉬운 것 외에도 실제로 작동해야한다고 생각합니다.

if (card1.getSet() > card2.getSet()) {
    return 1;
}
if (card1.getSet() < card2.getSet()) {
    return -1;
};
if (card1.getRarity() < card2.getRarity()) {
    return 1;
}
if (card1.getRarity() > card2.getRarity()) {
    return -1;
}
if (card1.getId() > card2.getId()) {
    return 1;
}
if (card1.getId() < card2.getId()) {
    return -1;
}
return cardType - item.getCardType();  //watch out for overflow!

15
실제로 제공 한 예제는 전이성을 위반하지 않지만 docs.oracle.com/javase/7/docs/에 명시된대로 sgn (compare (x, y)) == -sgn (compare (y, x)) 규칙을 위반하지 않습니다. api / java / util /… -수학적 측면에서 반대 칭입니다.
Hans-Peter Störr

1
if (card1.getSet() != card2.getSet()) return card1.getSet() > card2.getSet() ? 1 : -1;누군가 관심이 있다면 더 빠른 속도 로 사용 하는 것이 좋습니다 .
maaartinus

나는 이것을 만났고 비교기에서 대칭 문제 였습니다 . 진단하기 어려운 부분은 내 약점이 첫 번째 필드 (적절한 비교가 아닌 부울 필드 확인) 였는데, 둘 다 사실인지 확인하지 못했습니다. 그러나 이것은 정렬 순서를 교란해야하는 후속 하위 비교를 추가 한 후에 만 ​​노출되었습니다.
토마스 W

1
@maaartinus 귀하의 제안에 감사드립니다! 그것은 :) 나를 위해 완벽했다
Sonhja

45

다음 클래스를 사용하여 비교기에서 전이성 버그를 찾을 수 있습니다.

/**
 * @author Gili Tzabari
 */
public final class Comparators
{
    /**
     * Verify that a comparator is transitive.
     *
     * @param <T>        the type being compared
     * @param comparator the comparator to test
     * @param elements   the elements to test against
     * @throws AssertionError if the comparator is not transitive
     */
    public static <T> void verifyTransitivity(Comparator<T> comparator, Collection<T> elements)
    {
        for (T first: elements)
        {
            for (T second: elements)
            {
                int result1 = comparator.compare(first, second);
                int result2 = comparator.compare(second, first);
                if (result1 != -result2)
                {
                    // Uncomment the following line to step through the failed case
                    //comparator.compare(first, second);
                    throw new AssertionError("compare(" + first + ", " + second + ") == " + result1 +
                        " but swapping the parameters returns " + result2);
                }
            }
        }
        for (T first: elements)
        {
            for (T second: elements)
            {
                int firstGreaterThanSecond = comparator.compare(first, second);
                if (firstGreaterThanSecond <= 0)
                    continue;
                for (T third: elements)
                {
                    int secondGreaterThanThird = comparator.compare(second, third);
                    if (secondGreaterThanThird <= 0)
                        continue;
                    int firstGreaterThanThird = comparator.compare(first, third);
                    if (firstGreaterThanThird <= 0)
                    {
                        // Uncomment the following line to step through the failed case
                        //comparator.compare(first, third);
                        throw new AssertionError("compare(" + first + ", " + second + ") > 0, " +
                            "compare(" + second + ", " + third + ") > 0, but compare(" + first + ", " + third + ") == " +
                            firstGreaterThanThird);
                    }
                }
            }
        }
    }

    /**
     * Prevent construction.
     */
    private Comparators()
    {
    }
}

Comparators.verifyTransitivity(myComparator, myCollection)실패한 코드 앞에서 호출하기 만하면 됩니다.


3
이 코드는 compare (a, b) =-compare (b, c)의 유효성을 검사합니다. 비교 (a, b)> 0 && compare (b, c)> 0이면 비교 (a, c)> 0
Olaf Achthoven

3
나는 당신의 수업이 정말 매우 유용하다는 것을 알았습니다. 감사.
crigore

감사합니다.이 클래스는 비교기를 디버깅 할 때 매우 유용합니다. 여기에 언급 된 목적뿐만 아니라 다른 상황에서도 테스트 할 수 있도록 변경할 수 있기 때문에이 클래스는 매우 유용합니다.
Jaco-Ben Vosloo 2011

36

또한 JDK 버전과 관련이 있습니다. JDK6에서 잘 작동한다면 jdk 7의 구현 방법이 변경 되었기 때문에 JDK 7에서 설명한 문제가있을 수 있습니다.

이것 좀봐:

설명 : java.util.Arrays.sort및 (간접적으로) 사용되는 정렬 알고리즘 java.util.Collections.sort이 대체되었습니다. 새로운 정렬 구현은 계약 을 위반하는를 IllegalArgumentException감지 하면를 던질 수 있습니다 . 이전 구현에서는 이러한 상황을 조용히 무시했습니다. 이전 동작이 필요한 경우 새 시스템 속성을 사용하여 이전 병합 정렬 동작을 복원 할 수 있습니다 .ComparableComparablejava.util.Arrays.useLegacyMergeSort

정확한 이유를 모르겠습니다. 그러나 정렬을 사용하기 전에 코드를 추가하면. 괜찮을 거에요.

System.setProperty("java.util.Arrays.useLegacyMergeSort", "true");

1
문제는 내가 사물을 비교하는 데 사용한 논리에 있었다. 나는 이것을 upvoted 할 것입니다.이 유형의 문제를 찾는 사람을 도울 수 있기를 바랍니다.
Lakatos Gyula 2013

10
이것은 비교기를 고칠 때까지 일을 다시 작동하도록 만드는 빠르고 추한 해결 방법으로 만 사용해야합니다. 예외가 발생하면 비교기가 버그가 있고 정렬 순서가 이상하다는 것을 의미합니다. docs.oracle.com/javase/7/docs/api/java/util/…에 제공된 모든 규칙을 고려해야합니다 .
한스 - 피터 스토

"이상한"것이 내가 뭘하려고한다면? (a,b) -> { return (new int[]{-1,1})[(int)((Math.random()*2)%2)]}나는 Collections.shuffle존재한다는 것을 알고 있지만 지나치게 보호받는 것을 싫어합니다.
Jefferey Cave

오래된 응용 프로그램이 있고 JDK8에서이 오류가 발생했습니다. JDK6을 사용하면 올바르게 작동하므로 6으로 간단히 다운 그레이드합니다.
Stefano Scarpanti

6

다음 경우를 고려하십시오.

먼저 o1.compareTo(o2)호출됩니다. card1.getSet() == card2.getSet()true 인 경우도 마찬가지이므로 card1.getRarity() < card2.getRarity()1을 반환합니다.

그런 다음 o2.compareTo(o1)호출됩니다. 다시 말하지만 card1.getSet() == card2.getSet()사실입니다. 그런 다음 다음에 건너 else, 다음 card1.getId() == card2.getId()그래서 사실로 발생하며 cardType > item.getCardType(). 다시 1을 반환합니다.

그로부터 o1 > o2, 및 o2 > o1. 계약을 어겼습니다.


2
        if (card1.getRarity() < card2.getRarity()) {
            return 1;

그러나 card2.getRarity()이보다 작 으면 -1을card1.getRarity() 반환하지 않을 수 있습니다 .

마찬가지로 다른 사례를 놓칩니다. 나는 이것을 할 것이고, 당신의 의도에 따라 바꿀 수 있습니다.

public int compareTo(Object o) {    
    if(this == o){
        return 0;
    }

    CollectionItem item = (CollectionItem) o;

    Card card1 = CardCache.getInstance().getCard(cardId);
    Card card2 = CardCache.getInstance().getCard(item.getCardId());
    int comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }
    comp=card1.getRarity() - card2.getRarity();
    if (comp!=0){
        return comp;
    }
    comp=card1.getSet() - card2.getSet();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getId() - card2.getId();
    if (comp!=0){
        return comp;
    }   
    comp=card1.getCardType() - card2.getCardType();

    return comp;

    }
}

1

OpenJDK 버그 일 수도 있습니다 ... (이 경우는 아니지만 동일한 오류입니다)

나 같은 누군가가

java.lang.IllegalArgumentException: Comparison method violates its general contract!

Java 버전의 버그 일 수도 있습니다. 일부 응용 프로그램에서 몇 년 이후로 compareTo를 실행했습니다. 그러나 갑자기 작동이 중지되고 모든 비교가 완료된 후 오류가 발생합니다 ( "0"을 반환하기 전에 6 개의 속성을 비교합니다).

이제 OpenJDK의이 버그 보고서를 찾았습니다.


1

나는 같은 증상이 있었다. 나를 위해 Stream에서 정렬이 일어나는 동안 다른 스레드가 비교 된 객체를 수정하고 있음이 밝혀졌습니다. 이 문제를 해결하기 위해 개체를 변경 불가능한 임시 개체에 매핑하고 스트림을 임시 컬렉션에 수집하여 정렬했습니다.


0

여러 기준 (날짜, 같은 날짜라면 다른 것 ...)으로 분류해야했습니다. 이전 버전의 Java로 Eclipse에서 작업하는 것이 Android에서 더 이상 작동하지 않았습니다. 비교 방법이 계약을 위반합니다 ...

StackOverflow를 읽은 후 날짜가 같으면 compare ()에서 호출 한 별도의 함수를 작성했습니다. 이 함수는 기준에 따라 우선 순위를 계산하고 compare ()에 -1, 0 또는 1을 반환합니다. 이제 작동하는 것 같습니다.


0

다음과 같은 클래스에서 동일한 오류가 발생했습니다 StockPickBean. 이 코드에서 호출 :

List<StockPickBean> beansListcatMap.getValue();
beansList.sort(StockPickBean.Comparators.VALUE);

public class StockPickBean implements Comparable<StockPickBean> {
    private double value;
    public double getValue() { return value; }
    public void setValue(double value) { this.value = value; }

    @Override
    public int compareTo(StockPickBean view) {
        return Comparators.VALUE.compare(this,view); //return 
        Comparators.SYMBOL.compare(this,view);
    }

    public static class Comparators {
        public static Comparator<StockPickBean> VALUE = (val1, val2) -> 
(int) 
         (val1.value - val2.value);
    }
}

동일한 오류가 발생한 후 :

java.lang.IllegalArgumentException : 비교 메소드가 일반 계약을 위반합니다!

이 줄을 변경했습니다.

public static Comparator<StockPickBean> VALUE = (val1, val2) -> (int) 
         (val1.value - val2.value);

에:

public static Comparator<StockPickBean> VALUE = (StockPickBean spb1, 
StockPickBean spb2) -> Double.compare(spb2.value,spb1.value);

그러면 오류가 수정됩니다.


0

간단한 정수의 2D 배열 인 n x 2 2D array이름 을 정렬하려고 할 때 비슷한 문제가 발생했습니다 contests. 이것은 대부분의 시간 동안 작동했지만 한 입력에 대해 런타임 오류가 발생했습니다.

Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            } else return -1;
        });

오류:-

Exception in thread "main" java.lang.IllegalArgumentException: Comparison method violates its general contract!
    at java.base/java.util.TimSort.mergeHi(TimSort.java:903)
    at java.base/java.util.TimSort.mergeAt(TimSort.java:520)
    at java.base/java.util.TimSort.mergeForceCollapse(TimSort.java:461)
    at java.base/java.util.TimSort.sort(TimSort.java:254)
    at java.base/java.util.Arrays.sort(Arrays.java:1441)
    at com.hackerrank.Solution.luckBalance(Solution.java:15)
    at com.hackerrank.Solution.main(Solution.java:49)

위의 답변을 살펴보면 조건을 추가하려고 시도했지만 equals이유는 모르겠지만 효과가있었습니다. 바라건대 모든 경우 (보다 큼, 같음 및보다 작음)에 대해 반환되어야하는 내용을 명시 적으로 지정해야합니다.

        Arrays.sort(contests, (row1, row2) -> {
            if (row1[0] < row2[0]) {
                return 1;
            }
            if(row1[0] == row2[0]) return 0;
            return -1;
        });
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.