Errore Java: il metodo di confronto viola il suo contratto generale

Ho visto molte domande al riguardo e ho cercato di risolvere il problema, ma dopo un’ora di googling e un sacco di tentativi ed errori, non riesco ancora a risolverlo. Spero che alcuni di voi capiscano il problema.

Questo è quello che ottengo:

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

E questo è il mio comparatore:

 @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()  item.getCardType()) { return 1; } else { if (cardType == item.getCardType()) { return 0; } return -1; } } return -1; } } return 1; } } 

Qualche idea?

    Il messaggio di eccezione è in realtà piuttosto descrittivo. Il contratto menzionato è transitività : se A > B e B > C allora per qualsiasi A , B e C : A > C L’ho controllato con carta e matita e il tuo codice sembra avere pochi buchi:

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

    non si restituisce -1 se card1.getRarity() > card2.getRarity() .


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

    Ritorna -1 se gli ID non sono uguali. Dovresti restituire -1 o 1 seconda di quale ID è più grande.


    Guarda questo. Oltre ad essere molto più leggibile, penso che dovrebbe funzionare davvero:

     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! 

    Ha anche qualcosa a che fare con la versione di JDK. Se fa bene in JDK6, forse avrà il problema in JDK 7 descritto da te, perché il metodo di implementazione in jdk 7 è stato cambiato.

    Guarda questo:

    Descrizione: l’algoritmo di ordinamento utilizzato da java.util.Arrays.sort e (indirettamente) da java.util.Collections.sort è stato sostituito. La nuova implementazione sort può lanciare un IllegalArgumentException se rileva un Comparable che viola il contratto Comparable . L’implementazione precedente ignorò silenziosamente una situazione del genere. Se si desidera il comportamento precedente, è ansible utilizzare la nuova proprietà di sistema, java.util.Arrays.useLegacyMergeSort , per ripristinare il precedente comportamento di mergesort.

    Non conosco la ragione esatta. Tuttavia, se aggiungi il codice prima di utilizzare l’ordinamento. Sarà ok.

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

    Puoi utilizzare la seguente class per individuare i bug di transitività nei tuoi Comparatori:

     /** * @author Gili Tzabari */ public final class Comparators { /** * Verify that a comparator is transitive. * * @param  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  void verifyTransitivity(Comparator comparator, Collection 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() { } } 

    Basta invocare Comparators.verifyTransitivity(myComparator, myCollection) davanti al codice che fallisce.

    Considera il seguente caso:

    Innanzitutto, viene chiamato o1.compareTo(o2) . card1.getSet() == card2.getSet() sembra essere vero e lo è anche card1.getRarity() < card2.getRarity() , quindi restituisci 1.

    Quindi, viene chiamato o2.compareTo(o1) . Ancora una volta, card1.getSet() == card2.getSet() è true. Quindi, si salta al seguente, quindi card1.getId() == card2.getId() è vero, e così è cardType > item.getCardType() . Si ritorna di nuovo 1.

    Da ciò, o1 > o2 e o2 > o1 . Hai rotto il contratto.

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

    Tuttavia, se card2.getRarity() è inferiore a card1.getRarity() potresti non restituire -1 .

    Allo stesso modo, manchi altri casi. Lo farei, puoi cambiare in base al tuo intento:

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

    Ho dovuto ordinare su diversi criteri (data e, se stessa data, altre cose …). Quello che stava lavorando su Eclipse con una versione precedente di Java, non funzionava più su Android: il metodo di confronto viola il contratto …

    Dopo aver letto su StackOverflow, ho scritto una funzione separata che ho chiamato da compare () se le date sono le stesse. Questa funzione calcola la priorità, in base ai criteri, e restituisce -1, 0 o 1 per confrontare (). Sembra funzionare ora.

    Ho ottenuto lo stesso errore con una class come il seguente StockPickBean . Chiamato da questo codice:

     List beansListcatMap.getValue(); beansList.sort(StockPickBean.Comparators.VALUE); public class StockPickBean implements Comparable { 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 VALUE = (val1, val2) -> (int) (val1.value - val2.value); } } 

    Dopo aver ottenuto lo stesso errore:

    java.lang.IllegalArgumentException: il metodo di confronto viola il suo contratto generale!

    Ho cambiato questa linea:

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

    a:

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

    Questo corregge l’errore.