2010-03-13 3 views
0

Je crois que cette question s'applique aussi bien à C# qu'à Java, car les deux exigent que {c, C} omparePour être compatible avec {e, E} quals:C#/Java: Implémentation correcte de CompareTo quand Equals teste l'identité de référence

Supposons que je veux que mes equals() pour être identique à une vérification de référence, à savoir:

public bool equals(Object o) { 
    return this == o; 
} 

Dans ce cas, comment puis-je mettre compareTo (de Object o) (ou son équivalent générique)? Une partie est facile, mais je ne suis pas sûr de l'autre partie:

public int compareTo(Object o) { 
    MyClass other = (MyClass)o; 
    if (this == other) { 
     return 0; 
    } else { 
     int c = foo.CompareTo(other.foo) 
     if (c == 0) { 
      // what here? 
     } else { 
      return c; 
     } 
    } 
} 

Je ne peux pas revenir aveuglément 1 ou -1, parce que la solution doit se conformer aux exigences normales de compareTo. Je peux vérifier tous les champs d'instance, mais s'ils sont tous égaux, j'aimerais toujours que compareTo renvoie une valeur autre que 0. Il devrait être vrai que a.compareTo (b) == - (b.compareTo (a)), et l'ordre devrait rester cohérent tant que l'état des objets ne change pas. Cependant, je ne me soucie pas de commander des invocations de la machine virtuelle. Cela me fait penser que je pourrais utiliser quelque chose comme l'adresse mémoire, si je pouvais y arriver. Là encore, peut-être que cela ne fonctionnera pas, parce que le Garbage Collector pourrait décider de déplacer mes objets.

hashCode est une autre idée, mais je voudrais quelque chose qui sera toujours unique, et pas seulement la plupart du temps unique.

Des idées?

+0

Je ne comprends pas. S'il n'y a pas de commande raisonnable * logique * sur les objets, pourquoi auriez-vous besoin d'avoir une commande? Ne vous embêtez pas à implémenter Comparable si vos objets ne sont pas logiquement comparables entre eux. Quel problème essayez-vous de résoudre? – Joren

+0

Vous pouvez utiliser Object.ReferenceEquals (a, b) pour vérifier s'il s'agit du même objet. –

+0

@Joren: Mes objets ont un ordre logique, mais une fois que j'ai parcouru tout leur état, j'ai toujours besoin de comparer à égal pour égaler quand il vérifie l'égalité de référence. –

Répondre

3

Tout d'abord, si vous utilisez Java 5 ou plus, vous devez mettre en œuvre Comparable<MyClass> plutôt que l'ancienne Comparable plaine, donc votre méthode compareTo doit prendre des paramètres de type MyClass, non Object:

public int compareTo(MyClass other) { 
    if (this == other) { 
     return 0; 
    } else { 
     int c = foo.CompareTo(other.foo) 
     if (c == 0) { 
      // what here? 
     } else { 
      return c; 
     } 
    } 
} 

Comme de votre question, Josh Bloch en Java efficace (chapitre 3, point 12) dit:

Le implémenteur doit veiller à ce SGN (x.compareTo (y)) == -sgn (y.compare- Pour (x)) pour tout x et y. (Cela implique que x.compareTo (y) doit lancer une exception si et seulement si y.compareTo (x) renvoie une exception.)

Cela signifie que si c == 0 dans le code ci-dessus, vous doit retourner 0.Cela signifie que vous pouvez avoir les objets A et B, qui ne sont pas égaux, mais leur comparaison retourne 0. Qu'est-ce que M. Bloch a à dire à ce propos?

Il est fortement recommandé, mais pas strictement nécessaire, que (x.compareTo (y) == 0) == (x.equals (y)). D'une manière générale, toute classe qui implémente l'interface comparable et viole cette condition devrait indiquer clairement ce fait. La langue recommandée est « Note: Cette classe a un ordre naturel qui est incompatible avec égaux. »

Et

Une classe dont la méthode compareTo impose un ordre qui est incompatible avec égaux sera toujours travail, mais les collections triées contenant éléments de la classe peuvent ne pas obéir au contrat général de la collection appropriée interfaces (Collection, Set ou Map). En effet, les contrats généraux pour ces interfaces sont définis en termes de méthode égale, mais les collections triées utilisent le test d'égalité imposé par compareTo à la place d'égales. Ce n'est pas une catastrophe si cela se produit, mais c'est quelque chose dont il faut être conscient.

Mise à jour: donc à mon humble avis avec votre classe actuelle, vous ne pouvez pas faire compareTo compatible avec equals. Si vous avez vraiment besoin de cela, la seule façon dont je vois serait d'introduire un nouveau membre, ce qui donnerait un ordre naturel strict à votre classe. Ensuite, dans le cas où tous les champs significatifs des deux objets se comparent à 0, vous pouvez toujours décider de l'ordre des deux en fonction de leurs valeurs de commande spéciales.

Ce membre supplémentaire peut être un compteur d'instances ou un horodatage de création. Ou, vous pouvez essayer d'utiliser un UUID.

+0

Ma question est précisément Comment puis-je comparer à égal à égal lorsque égal à seulement des tests pour l'égalité de référence? –

+0

@Paul A Jungwirth OK, je comprends :-) Pls voir ma mise à jour. –

+0

Hmm, le compteur et l'identifiant unique sont deux idées possibles. Savez-vous si C# a quelque chose de comparable à l'identifiant unique de Java? –

0

L'équivalent générique est plus facile à traiter à mon avis, dépend de ce que vos besoins externes, c'est un IComparable<MyClass> exemple:

public int CompareTo(MyClass other) { 
    if (other == null) return 1; 
    if (this == other) { 
     return 0; 
    } else { 
     return foo.CompareTo(other.foo); 
    } 
} 

Si les classes sont égales ou si foo est égal, c'est la fin de la comparaison, à moins qu'il y a quelque chose de secondaire à trier, dans ce cas ajouter que le retour si foo.CompareTo(other.foo) == 0

Si vos classes ont une carte d'identité ou quelque chose, puis comparer ce comme secondaire, sinon ne vous inquiétez pas. ..la collection, ils sont stockés et il est d'ordre pour arriver à ces Les classes à comparer sont ce qui va déterminer l'ordre final dans le cas d'objets égaux ou de valeurs object.foo égales.

1

Deux objets n'ont pas besoin d'être de référence égale pour être dans la même classe d'équivalence. À mon avis, il devrait être parfaitement acceptable que deux objets différents soient identiques pour une comparaison, mais pas une référence égale. Il me semble parfaitement naturel, par exemple, que si vous avez hydraté deux objets différents d'une même ligne dans la base de données, ils seraient identiques à des fins de comparaison, mais pas de référence égale.

Je serais en fait plus enclin à modifier le comportement des égaux pour refléter la façon dont ils sont comparés plutôt que l'inverse. Pour la plupart des choses que je peux penser à cela serait plus naturel.

2

En Java ou en C#, en règle générale, il n'y a pas d'ordre fixe d'objets. Les instances peuvent être déplacées par le ramasse-miettes lors de l'exécution de votre compareTo ou l'opération de tri qui utilise votre compareTo. Comme vous l'avez dit, les codes de hachage ne sont généralement pas uniques, ils ne sont donc pas utilisables (deux instances différentes avec le même code de hachage vous ramènent à la question d'origine). Et l'implémentation Java Object.toString que beaucoup de gens croient faire surface à un identifiant d'objet (MyObject @ 33c0d9d), n'est rien de plus que le nom de classe de l'objet suivi du code de hachage. Pour autant que je sache, ni la JVM ni la CLR n'ont une notion d'identifiant d'instance.

Si vous voulez vraiment un classement cohérent de vos classes, vous pouvez essayer d'utiliser un nombre incrémentiel pour chaque nouvelle instance que vous créez. Rappelez-vous, l'incrémentation de ce compteur doit être thread safe, donc ça va être relativement cher (en C# vous pouvez utiliser Interlocked.Increment).

Questions connexes