221 votes

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

Quelqu'un peut-il m'expliquer en termes simples pourquoi ce code lève une exception, "La méthode de comparaison viole son contrat général !", et comment puis-je y remédier ?

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

1 votes

Quels sont le nom et la classe de l'exception ? S'agit-il d'une exception de type IllegalArgumentException ? Si je devais deviner, je penserais que vous devriez faire s1.getParent().equals(s2) au lieu de s1.getParent() == s2 .

0 votes

Ainsi que l'exception qui est levée.

2 votes

Je ne connais pas très bien Java ou les API de comparaison de Java, mais cette méthode de comparaison semble tout à fait erronée. Supposons que s1 est le parent de s2 y s2 n'est pas le parent de s1 . Ensuite compareParents(s1, s2) es 0 mais compareParents(s2, s1) es 1 . Cela n'a pas de sens. (De plus, il n'est pas transitif, comme aix mentionné ci-dessous).

297voto

NPE Points 169956

Votre comparateur n'est pas transitif.

Sea A être le parent de B y B être le parent de C . Depuis le A > B y B > C , alors il faut que ce soit le cas A > C . Cependant, si votre comparateur est invoqué sur A y C il renverrait zéro, ce qui signifie que A == C . Cela constitue une violation du contrat et entraîne donc une 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é en compareParents() est de traverser le getParent() au lieu de s'intéresser uniquement à l'ancêtre immédiat.

4 votes

Introduit dans la version 7 de Java java.util.Arrays.sort stackoverflow.com/questions/7849539/

64 votes

Le fait que la bibliothèque détecte ce phénomène est impressionnant. Quelqu'un à Sun mérite de lancer un gigantesque Il n'y a pas de quoi .

1 votes

Pouvez-vous généraliser un peu cette réponse afin de rendre cette question plus utile en tant que poste de référence ?

46voto

you786 Points 1793

C'est ce que j'ai obtenu lorsque j'ai cherché cette erreur sur Google.

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

les value >= other.value devrait (évidemment) être value > other.value afin de pouvoir renvoyer 0 avec des objets égaux.

9 votes

Je dois ajouter que si l'un de vos value est un NaN (si value est un double o float ), il échouerait également.

33voto

CESDewar Points 41

La violation du contrat signifie souvent que le comparateur ne fournit pas une valeur correcte ou cohérente lorsqu'il compare des objets. Par exemple, vous pourriez vouloir effectuer une comparaison de chaînes de caractères et forcer les chaînes vides à être triées à la fin 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 cela ne tient pas compte du cas où LES DEUX sont vides - et dans ce cas, la mauvaise valeur est renvoyée (1 au lieu de 0 pour indiquer une correspondance), et le comparateur signale qu'il y a violation. Il aurait fallu l'écrire comme suit :

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

4voto

keesp Points 78

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

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

3voto

Martin Points 149

Java ne vérifie pas la cohérence au sens strict, mais vous avertit uniquement en cas de problème grave. Il ne vous donne pas non plus beaucoup d'informations sur l'erreur.

J'étais perplexe quant à ce qui se passait dans ma trieuse et j'ai créé un vérificateur de cohérence strict, 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));
    }
  }
}

0 votes

Il suffit d'invoquer la méthode checkConsitency avec une liste de paramètres et un comparateur.

0 votes

Votre code ne se compile pas. Classes Compare , Convert (et potentiellement d'autres) ne sont pas définis. Veuillez mettre à jour le code sniplet avec un exemple autonome.

0 votes

Vous devriez corriger la faute de frappe dans checkConsi(s)tency et supprimer tous les @param pour rendre le code plus lisible.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X