web-dev-qa-db-fra.com

"La méthode de comparaison viole son contrat général!"

Quelqu'un peut-il m'expliquer en termes simples, pourquoi ce code jette-t-il une exception, "La méthode de comparaison contrevient-elle à son contrat général!", Et comment puis-je résoudre ce problème?

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

Votre comparateur n'est pas transitif.

Soit A le parent de B et B le parent de C. Puisque A > B et B > C, il doit alors s'agir de A > C. Cependant, si votre comparateur est appelé sur A et C, il retournera zéro, ce qui signifie A == C. Cela viole le contrat et lance donc l'exception.

C'est plutôt gentil de la part de la bibliothèque de détecter cela et de vous le faire savoir, plutôt que de se comporter de manière erratique.

Une façon de satisfaire l'exigence de transitivité dans compareParents() consiste à parcourir la chaîne getParent() au lieu de ne regarder que l'ancêtre immédiat.

247
NPE

Juste parce que c’est ce que j’ai eu lorsque j’ai googlé cette erreur, mon problème était que j’avais

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

le value >= other.value devrait (évidemment) être en fait value > other.value afin que vous puissiez réellement renvoyer 0 avec des objets égaux.

35
you786

La violation du contrat signifie souvent que le comparateur ne fournit pas la valeur correcte ou cohérente lors de la comparaison d'objets. Par exemple, vous pouvez effectuer une comparaison de chaîne et forcer le tri à la fin des chaînes vides avec:

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

Mais ceci néglige le cas où les DEUX points un et deux sont vides - et dans ce cas, la valeur erronée est renvoyée (1 au lieu de 0 pour indiquer une correspondance) et le comparateur le signale comme une violation. Cela aurait dû être écrit comme:

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

Même si votre point de comparaison tient en théorie la transitivité, des bugs parfois subtils gâchent des choses… telles que des erreurs arithmétiques en virgule flottante. Il m'est arrivé c'était mon code:

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;

}   

La propriété transitive est clairement valable, mais pour une raison quelconque, je recevais l'exception IllegalArgumentException. Et il s'avère qu'en raison de petites erreurs d'arithmétique en virgule flottante, les erreurs d'arrondi ont provoqué la rupture de la propriété transitive là où elle ne devrait pas! J'ai donc réécrit le code pour prendre en compte de très petites différences 0, et cela a fonctionné:

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;
}   
11
Ali Mizan

Dans notre cas, nous recevions cette erreur parce que nous avions accidentellement inversé l’ordre de comparaison des valeurs de s1 et s2. Alors faites attention à cela. C'était évidemment beaucoup plus compliqué que ce qui suit, mais voici une illustration:

s1 == s2   
    return 0;
s2 > s1 
    return 1;
s1 < s2 
    return -1;
6
Uncle Iroh

Dans mon cas, je faisais quelque chose comme ceci:

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

Ce que j'ai oublié de vérifier, c’est quand a.someField et b.someField sont nuls.

3
jc12

Java ne vérifie pas la cohérence de manière stricte, il vous avertit uniquement en cas de problème grave. En outre, cela ne vous donne pas beaucoup d'informations sur l'erreur.

J'ai été intrigué par ce qui se passe dans ma trieuse et j'ai fait une vérification cohérente stricte. Peut-être que cela vous aidera:

/**
 * @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));
    }
  }
}
3
Martin

J'ai vu cela se produire dans un morceau de code où la vérification souvent récurrente des valeurs nulles a été effectuée:

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

Si compareParents(s1, s2) == -1, alors compareParents(s2, s1) == 1 est attendu. Avec votre code, ce n'est pas toujours vrai.

Spécifiquement si s1.getParent() == s2 && s2.getParent() == s1. C'est juste l'un des problèmes possibles.

1

Éditer VM La configuration a fonctionné pour moi.

-Djava.util.Arrays.useLegacyMergeSort=true
0
Rishav Roy

Vous ne pouvez pas comparer les données d'objet comme ceci: s1.getParent() == s2 - ceci comparera les références d'objet. Vous devez remplacer equals function pour la classe Foo puis les comparer comme ceci s1.getParent().equals(s2)

0
erimerturk