Pourquoi C # et Java utilisent-ils l'égalité de référence par défaut pour '=='?

32

Je réfléchis depuis un moment pourquoi Java et C # (et je suis sûr que d'autres langages) par défaut font référence à l'égalité pour ==.

Dans la programmation que je fais (qui n'est certainement qu'un petit sous-ensemble de problèmes de programmation), je veux presque toujours l'égalité logique lors de la comparaison d'objets au lieu de l'égalité de référence. J'essayais de penser pourquoi ces deux langues ont choisi cette voie au lieu de l'inverser et d'avoir ==une égalité logique et une égalité .ReferenceEquals()de référence.

De toute évidence, l'utilisation de l'égalité de référence est très simple à mettre en œuvre et donne un comportement très cohérent, mais il ne semble pas que cela cadre bien avec la plupart des pratiques de programmation que je vois aujourd'hui.

Je ne souhaite pas sembler ignorer les problèmes liés à l'implémentation d'une comparaison logique, et qu'elle doit être implémentée dans chaque classe. Je me rends également compte que ces langues ont été conçues il y a longtemps, mais la question générale demeure.

Y a-t-il un avantage majeur à faire défaut à cela que je manque simplement, ou semble-t-il raisonnable que le comportement par défaut soit l'égalité logique, et par défaut à l'égalité de référence, une égalité logique n'existe pas pour la classe?

Fermeture éclair
la source
3
Parce que les variables sont des références? Puisque les variables agissent comme des pointeurs, il est logique qu'elles soient comparées comme ça
Daniel Gratzer
C # utilise l'égalité logique pour les types de valeur comme les structures. Mais que devrait être "l'égalité logique par défaut" pour deux objets de types de référence différents? Ou pour deux objets dont l'un est de type A hérité de B? Toujours "faux" comme pour les structures? Même lorsque vous avez le même objet référencé deux fois, d'abord comme A, puis comme B? Cela n'a pas beaucoup de sens pour moi.
Doc Brown
3
En d'autres termes, demandez-vous pourquoi en C #, si vous remplacez Equals(), cela ne change pas automatiquement le comportement de ==?
svick

Réponses:

29

C # le fait parce que Java l'a fait. Java l'a fait car Java ne prend pas en charge la surcharge des opérateurs. Étant donné que l'égalité des valeurs doit être redéfinie pour chaque classe, il ne peut pas s'agir d'un opérateur, mais doit plutôt être une méthode. OMI, c'était une mauvaise décision. Il est beaucoup plus facile à la fois d'écrire et de lire a == bque a.equals(b), et beaucoup plus naturel pour les programmeurs ayant une expérience en C ou C ++, mais a == bc'est presque toujours faux. Les bogues liés à l'utilisation de l' ==emplacement .equalsrequis ont fait perdre des milliers d'heures de programmation.

Kevin Cline
la source
7
Je pense qu'il y a autant de partisans de la surcharge des opérateurs qu'il y a de détracteurs, donc je ne dirais pas "c'était une mauvaise décision" comme une déclaration absolue. Exemple: dans le projet C ++ dans lequel je travaille, nous avons surchargé le ==pour de nombreuses classes et il y a quelques mois, j'ai découvert que quelques développeurs ne savaient pas ce qui se ==passait réellement. Il y a toujours ce risque lorsque la sémantique d'une construction n'est pas évidente. La equals()notation me dit que j'utilise une méthode personnalisée et que je dois la chercher quelque part. Bottom line: Je pense que la surcharge des opérateurs est un problème ouvert en général.
Giorgio
9
Je dirais que Java n'a pas de surcharge d'opérateur définie par l'utilisateur . De nombreux opérateurs ont une double signification (surchargée) en Java. Regardez +par exemple, qui fait l'addition (de valeurs numériques) et la concaténation de chaînes en même temps.
Joachim Sauer
14
Comment peut-il a == bêtre plus naturel pour les programmeurs ayant une expérience C, puisque C ne prend pas en charge la surcharge d'opérateur définie par l'utilisateur? (Par exemple, la façon C de comparer les chaînes est strcmp(a, b) == 0non a == b.)
svick
C'est essentiellement ce que je pensais, mais j'ai pensé que je demanderais à ceux qui ont plus d'expérience de s'assurer que je ne manquais pas quelque chose d'évident.
Fermeture éclair du
4
@svick: en C, il n'y a pas de type chaîne, ni de type référence. Les opérations sur les chaînes sont effectuées via char *. Il me semble évident que comparer deux pointeurs pour l'égalité n'est pas la même chose qu'une comparaison de chaînes.
kevin cline
15

La réponse courte: la cohérence

Pour répondre correctement à votre question, je suggère que nous prenions un pas en arrière et examinions la question de savoir ce que signifie l'égalité dans un langage de programmation. Il existe au moins TROIS possibilités différentes, qui sont utilisées dans différentes langues:

  • Égalité de référence : signifie que a = b est vrai si a et b font référence au même objet. Ce ne serait pas vrai si a et b faisaient référence à des objets différents, même si tous les attributs de a et b étaient les mêmes.
  • Peu d'égalité : signifie que a = b est vrai si tous les attributs des objets auxquels a et b se réfèrent sont identiques. L'égalité superficielle peut facilement être implémentée par une comparaison au niveau du bit de l'espace mémoire qui représente les deux objets. Veuillez noter que l'égalité de référence implique une égalité superficielle
  • Égalité profonde : signifie que a = b est vrai si chaque attribut de a et b est identique ou profondément égal. Veuillez noter que l'égalité profonde est impliquée à la fois par l'égalité de référence et l'égalité superficielle. En ce sens, l'égalité profonde est la forme d'égalité la plus faible et l'égalité de référence est la plus forte.

Ces trois types d'égalité sont souvent utilisés car ils sont pratiques à mettre en œuvre: les trois vérifications d'égalité peuvent facilement être générées par un compilateur (dans le cas d'une égalité profonde, le compilateur peut avoir besoin d'utiliser des bits de balise pour éviter les boucles infinies si une structure à être comparé a des références circulaires). Mais il y a un autre problème: aucun de ceux-ci pourrait être approprié.

Dans les systèmes non triviaux, l'égalité des objets est souvent définie comme quelque chose entre l'égalité profonde et l'égalité de référence. Pour vérifier si nous voulons considérer deux objets comme égaux dans un certain contexte, nous pourrions exiger que certains attributs soient comparés par où ils se trouvent dans la mémoire et d'autres par une égalité profonde, tandis que certains attributs peuvent être autorisés à être quelque chose de complètement différent. Ce que nous aimerions vraiment, c'est un «quatrième type d'égalité», vraiment sympa, souvent appelé égalité sémantique dans la littérature . Les choses sont égales si elles sont égales, dans notre domaine. =)

Nous pouvons donc revenir à votre question:

Y a-t-il un avantage majeur à faire défaut à cela que je manque simplement, ou semble-t-il raisonnable que le comportement par défaut soit l'égalité logique, et qu'il revienne par défaut à l'égalité de référence s'il n'y a pas d'égalité logique pour la classe?

Que voulons-nous dire lorsque nous écrivons «a == b» dans n'importe quelle langue? Idéalement, cela devrait toujours être le même: l'égalité sémantique. Mais ce n'est pas possible.

L'une des principales considérations est que, au moins pour les types simples comme les nombres, nous nous attendons à ce que deux variables soient égales après attribution de la même valeur. Voir ci-dessous:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

Dans ce cas, nous nous attendons à ce que «a est égal à b» dans les deux déclarations. Tout le reste serait fou. La plupart (sinon toutes) des langues suivent cette convention. Par conséquent, avec des types simples (ou valeurs), nous savons comment atteindre l'égalité sémantique. Avec les objets, cela peut être quelque chose de complètement différent. Voir ci-dessous:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

Nous nous attendons à ce que le premier «si» soit toujours vrai. Mais qu'attendez-vous du second «si»? Ça dépend vraiment. 'DoSomething' peut-il changer l'égalité (sémantique) de a et b?

Le problème de l'égalité sémantique est qu'elle ne peut pas être générée automatiquement par le compilateur pour les objets, ni évidente à partir des affectations . Un mécanisme doit être fourni à l'utilisateur pour définir l'égalité sémantique. Dans les langages orientés objet, ce mécanisme est une méthode héritée: equals . En lisant un morceau de code OO, nous ne nous attendons pas à ce qu'une méthode ait la même implémentation exacte dans toutes les classes. Nous sommes habitués à l'héritage et à la surcharge.

Avec les opérateurs, cependant, nous nous attendons au même comportement. Lorsque vous voyez 'a == b', vous devriez vous attendre au même type d'égalité (des 4 ci-dessus) dans toutes les situations. Ainsi, dans un souci de cohérence, les concepteurs de langages ont utilisé l'égalité de référence pour tous les types. Cela ne devrait pas dépendre du fait qu'un programmeur a remplacé une méthode ou non.

PS: Le langage Dee est légèrement différent de Java et C #: l'opérateur égal signifie égalité superficielle pour les types simples et égalité sémantique pour les classes définies par l'utilisateur (la responsabilité de la mise en œuvre de l'opération = incombant à l'utilisateur - aucune valeur par défaut n'est fournie). Comme pour les types simples, l'égalité superficielle est toujours l'égalité sémantique, le langage est cohérent. Le prix à payer, cependant, est que l'opérateur égal n'est pas défini par défaut pour les types définis par l'utilisateur. Vous devez le mettre en œuvre. Et, parfois, c'est juste ennuyeux.

Hbas
la source
2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Les concepteurs de langage de Java ont utilisé l'égalité de référence pour les objets et l'égalité sémantique pour les primitives. Ce n'est pas évident pour moi que c'était la bonne décision, ou que cette décision est plus "cohérente" que ==de se laisser surcharger pour l'égalité sémantique des objets.
Charles Salvia
Ils ont également utilisé «l'équivalent de l'égalité de référence» pour les primitives. Lorsque vous utilisez "int i = 3", il n'y a pas de pointeurs pour le nombre, vous ne pouvez donc pas utiliser de références. Avec des chaînes, une sorte de type primitif, c'est plus évident: vous devez utiliser le ".intern ()" ou une affectation directe (String s = "abc") pour utiliser == (égalité de référence).
Hbas
1
PS: C #, d'autre part, n'était pas compatible avec ses cordes. Et à mon humble avis, dans ce cas, c'est beaucoup mieux.
Hbas
@CharlesSalvia: En Java, si aet bsont du même type, l'expression a==bteste si aet bdétient la même chose. Si l'un d'eux contient une référence à l'objet # 291, et l'autre contient une référence à l'objet # 572, ils ne contiennent pas la même chose. Le contenu des objets # 291 et # 572 peut être équivalent, mais les variables elles-mêmes contiennent des choses différentes.
supercat
2
@CharlesSalvia Il est conçu de telle manière que vous pouvez voir a == bet savoir ce qu'il fait. De même, vous pouvez voir a.equals(b)et présumer qu'une surcharge equals. S'il s'agit d' a == bappels a.equals(b)(s'ils sont mis en œuvre), s'agit-il d'une comparaison par référence ou par contenu? Tu ne te souviens pas? Vous devez vérifier la classe A. Le code n'est plus aussi rapide à lire si vous n'êtes même pas sûr de ce qui est appelé. Ce serait comme si des méthodes avec la même signature étaient autorisées, et la méthode appelée dépend de la portée actuelle. De tels programmes seraient impossibles à lire.
Neil
0

J'essayais de penser pourquoi ces deux langages ont choisi cette voie au lieu de l'inverser et d'avoir == une égalité logique et d'utiliser .ReferenceEquals () pour l'égalité de référence.

Parce que cette dernière approche serait source de confusion. Considérer:

if (null.ReferenceEquals(null)) System.out.println("ok");

Ce code "ok"doit-il s'imprimer ou doit-il lancer un NullPointerException?

Atsby
la source
-2

Pour Java et C #, l'avantage réside dans le fait qu'ils sont orientés objet.

Du point de vue des performances - le code plus facile à écrire devrait également être plus rapide: étant donné que la POO a l'intention que des éléments logiquement distincts soient représentés par différents objets, la vérification de l'égalité des références serait plus rapide, compte tenu du fait que les objets peuvent devenir assez volumineux.

D'un point de vue logique - l'égalité d'un objet à un autre ne doit pas être aussi évidente que la comparaison avec les propriétés de l'objet pour l'égalité (par exemple, comment null == null est-il interprété logiquement? Cela peut différer d'un cas à l'autre).

Je pense que cela se résume à votre observation selon laquelle "vous voulez toujours l'égalité logique sur l'égalité de référence". Le consensus parmi les concepteurs de langage était probablement le contraire. Personnellement, j'ai du mal à évaluer cela, car je n'ai pas le large éventail d'expérience en programmation. En gros, j'utilise davantage l'égalité de référence dans les algorithmes d'optimisation et l'égalité logique dans la gestion des ensembles de données.

Rafael Emshoff
la source
7
L'égalité de référence n'a rien à voir avec l'orientation objet. Au contraire, en fait: l'une des propriétés fondamentales de l'orientation objet est que les objets qui ont le même comportement sont indiscernables. Un objet doit pouvoir simuler un autre objet. (Après tout, OO a été inventé pour la simulation!) L'égalité de référence vous permet de distinguer entre deux objets différents qui ont le même comportement, il vous permet de faire la distinction entre un objet simulé et un objet réel. Par conséquent, l'égalité de référence rompt l' orientation de l'objet. Un programme OO ne doit pas utiliser l'égalité de référence.
Jörg W Mittag
@ JörgWMittag: Pour faire correctement un programme orienté objet, il doit y avoir un moyen de demander à l'objet X si son état est égal à celui de Y [une condition potentiellement transitoire], et aussi un moyen de demander à l'objet X s'il est équivalent à Y [X n'est équivalent à Y que si son état est garanti éternellement égal à Y]. Avoir des méthodes virtuelles distinctes pour l'équivalence et l'égalité d'état serait bien, mais pour de nombreux types, l'inégalité de référence impliquera la non équivalence, et il n'y a aucune raison de consacrer du temps à la répartition des méthodes virtuelles pour le prouver.
supercat
-3

.equals()compare les variables par leur contenu. au lieu de ==cela compare les objets par leur contenu ...

l'utilisation d'objets est une utilisation plus précise de tu .equals()

Nuno Dias
la source
3
Votre hypothèse est incorrecte. .equals () fait ce que .equals () a été codé pour faire. C'est normalement par le contenu, mais ce n'est pas obligatoire. De plus, il n'est pas plus précis d'utiliser .equals (). Cela dépend simplement de ce que vous essayez d'accomplir.
Fermeture éclair