web-dev-qa-db-fra.com

Pourquoi la comparaison de la valeur de la chaîne de l'opérateur == n'est-elle pas arrivée à Java?

Chaque Java Java compétent sait que vous devez utiliser String.equals () pour comparer une chaîne, plutôt que == car == vérifie l'égalité de référence.

Lorsque je traite des chaînes, la plupart du temps je vérifie l'égalité de valeur plutôt que l'égalité de référence. Il me semble qu'il serait plus intuitif que la langue permette de comparer les valeurs de chaîne en utilisant simplement ==.

A titre de comparaison, l'opérateur == de C # vérifie l'égalité de valeur pour la chaîne s. Et si vous aviez vraiment besoin de vérifier l'égalité de référence, vous pouvez utiliser String.ReferenceEquals.

Un autre point important est que les chaînes sont immuables, il n'y a donc aucun mal à faire en autorisant cette fonctionnalité.

Y a-t-il une raison particulière pour laquelle cela n'est pas implémenté en Java?

50
l46kok

Je suppose que c'est juste de la cohérence, ou "principe du moindre étonnement". La chaîne est un objet, il serait donc surprenant qu'elle soit traitée différemment des autres objets.

Au moment où Java est sorti (~ 1995), le simple fait d'avoir quelque chose comme String était un luxe total pour la plupart des programmeurs qui étaient habitués à représenter les chaînes comme des tableaux à terminaison nulle. Le comportement de String est maintenant ce qu'il était à l'époque, et c'est bien; changer subtilement le comportement plus tard pourrait avoir des effets surprenants et indésirables dans les programmes de travail.

En remarque, vous pouvez utiliser String.intern() pour obtenir une représentation canonique (internée) de la chaîne, après quoi des comparaisons peuvent être faites avec ==. L'internement prend un certain temps, mais après cela, les comparaisons seront très rapides.

Addition: contrairement à ce que certaines réponses suggèrent, il ne s'agit pas de prendre en charge la surcharge des opérateurs. Le + opérateur (concaténation) fonctionne sur Strings même si Java ne prend pas en charge la surcharge d'opérateur; il est simplement géré comme un cas spécial dans le compilateur, en résolvant à StringBuilder.append(). De même, == aurait pu être traité comme un cas spécial.

Alors pourquoi étonner avec un cas spécial + mais pas avec ==? Parce que, + ne compile tout simplement pas lorsqu'il est appliqué à des objets non_String, ce qui apparaît rapidement. Le comportement différent de == serait beaucoup moins apparent et donc beaucoup plus étonnant quand il vous toucherait.

90
Joonas Pulakka

James Gosling , le créateur de Java, l'a expliqué de cette façon en juillet 20 :

J'ai omis la surcharge d'opérateur comme un choix assez personnel parce que j'avais vu trop de gens en abuser en C++. J'ai passé beaucoup de temps au cours des cinq à six dernières années à enquêter sur la surcharge des opérateurs et c'est vraiment fascinant, car la communauté est divisée en trois parties: environ 20 à 30% de la population pensent que la surcharge des opérateurs est la ponte du diable; quelqu'un a fait quelque chose avec la surcharge de l'opérateur qui vient de les cocher, car ils ont utilisé comme + pour l'insertion de liste et cela rend la vie vraiment, vraiment déroutante. Une grande partie de ce problème provient du fait qu'il n'y a qu'environ une demi-douzaine d'opérateurs que vous pouvez sensiblement surcharger, et pourtant il y a des milliers ou des millions d'opérateurs que les gens voudraient définir - vous devez donc choisir, et souvent les choix entrer en conflit avec votre sens de l'intuition.

32
Ross Patterson

Cohérence dans la langue. Avoir un opérateur qui agit différemment peut surprendre le programmeur. Java ne permet pas aux utilisateurs de surcharger les opérateurs - par conséquent, l'égalité de référence est la seule signification raisonnable pour == Entre les objets.

Dans Java:

  • Entre les types numériques, == Compare l'égalité numérique
  • Entre les types booléens, == Compare l'égalité booléenne
  • Entre les objets, == Compare l'identité de référence
    • Utilisez .equals(Object o) pour comparer les valeurs

C'est ça. Règle simple et simple pour identifier ce que vous voulez. Tout cela est couvert dans section 15.21 du JLS . Il comprend trois sous-sections faciles à comprendre, à mettre en œuvre et à expliquer.

Une fois que vous autorisez la surcharge de == , le comportement exact n'est pas quelque chose que vous pouvez regarder vers le JLS et mettre le doigt sur un élément spécifique et dire "c'est comme ça que ça fonctionne". le code peut devenir difficile à raisonner. Le comportement exact de == Peut surprendre un utilisateur. Chaque fois que vous le voyez, vous devez revenir en arrière et vérifier ce que cela signifie réellement.

Étant donné que Java ne permet pas la surcharge des opérateurs, il faut un moyen d'avoir un test d'égalité de valeur dont vous pouvez remplacer la définition de base. Ainsi, cela a été rendu obligatoire par ces choix de conception. == In Java teste numeric pour les types numériques, l'égalité booléenne pour les types booléens et l'égalité de référence pour tout le reste (qui peut remplacer .equals(Object o) pour faire ce qu'ils veulent) pour l’égalité des valeurs).

Ce n'est pas une question de "y a-t-il un cas d'utilisation pour une conséquence particulière de cette décision de conception" mais plutôt "c'est une décision de conception pour faciliter ces autres choses, c'est une conséquence de cela".

String interning , en est un exemple. Selon le JLS 3.10.5 , tous les littéraux de chaîne sont internés. D'autres chaînes sont internées si on invoque .intern() sur elles. Le fait que "foo" == "foo" Soit vrai est une conséquence des décisions de conception prises pour minimiser l'empreinte mémoire occupée par les littéraux String. Au-delà de cela, l'internement de chaînes est quelque chose qui est au niveau de la JVM qui a un peu d'exposition à l'utilisateur, mais dans l'immense majorité des cas, ne devrait pas être quelque chose qui concerne le programmeur (et les cas d'utilisation pour les programmeurs n'étaient pas quelque chose qui était en haut de la liste pour les concepteurs lors de l'examen de cette fonctionnalité).

Les gens remarqueront que + Et += Sont surchargés pour String. Mais ce n'est ni ici ni là-bas. Il reste que si == A une signification d'égalité de valeur pour String (et seulement String), il faudrait une méthode différente ( qui n'existe que dans String) pour l'égalité de référence. De plus, cela compliquerait inutilement les méthodes qui prennent Object et s'attendent à ce que == Se comporte d'une manière et que .equals() se comporte d'une autre, obligeant les utilisateurs à tout cas spécial ceux méthodes pour String.

Le contrat cohérent pour == Sur les objets est qu'il ne s'agit que d'égalité de référence et que .equals(Object o) existe pour tous les objets qui devraient test d'égalité de valeur. Compliquer cela complique beaucoup trop de choses.

9
user40980

Java ne prend pas en charge la surcharge des opérateurs, ce qui signifie == s'applique uniquement aux types ou références primitifs. Tout autre élément nécessite l'invocation d'une méthode. Pourquoi les concepteurs ont-ils fait cela? C'est une question à laquelle ils seuls peuvent répondre. Si je devais deviner, c'est probablement parce que la surcharge des opérateurs apporte de la complexité qu'ils n'étaient pas intéressés à ajouter.

Je ne suis pas un expert en C #, mais les concepteurs de ce langage semblent l'avoir configuré de telle sorte que chaque primitive est un struct et chaque struct est un objet. Parce que C # permet une surcharge d'opérateur, cet arrangement rend très facile pour n'importe quelle classe, pas seulement String, de se faire fonctionner de la manière "attendue" avec n'importe quel opérateur. C++ permet la même chose.

2
Blrfl

Cela a été rendu différent dans d'autres langues.

En Object Pascal (Delphi/Free Pascal) et C #, l'opérateur d'égalité est défini pour comparer les valeurs, pas les références, lors de l'utilisation de chaînes.

En particulier en Pascal, la chaîne est un type primitif (une des choses que j'aime vraiment chez Pascal, obtenir NullreferenceException juste à cause d'une chaîne non initialisée est tout simplement irritant) et avoir ainsi une sémantique de copie sur écriture ( la plupart du temps) les opérations de chaîne sont très bon marché (en d'autres termes, elles ne sont visibles que lorsque vous commencez à concaténer des chaînes de plusieurs mégaoctets).

C'est donc une décision de conception de langage pour Java. Quand ils ont conçu le langage, ils ont suivi la méthode C++ (comme Std :: String), donc les chaînes sont des objets, ce qui est à mon humble avis un hack pour compenser le manque d'un vrai type de chaîne en C, au lieu de faire des chaînes une primitive (ce qu'elles sont).

Donc, pour une raison, je ne peux que spéculer qu'ils ont fait cela trop facilement de leur côté et que le codage de l'opérateur ne fait pas exception sur le compilateur pour les chaînes.

2
Fabricio Araujo

En Java, il n'y a aucune surcharge d'opérateur, et c'est pourquoi les opérateurs de comparaison ne sont surchargés que pour les types primitifs.

La classe 'String' n'est pas une primitive, elle n'a donc pas de surcharge pour '==' et utilise la valeur par défaut de comparaison de l'adresse de l'objet dans la mémoire de l'ordinateur.

Je ne suis pas sûr, mais je pense qu'en Java 7 ou 8 Oracle a fait une exception dans le compilateur pour reconnaître str1 == str2 Comme str1.equals(str2)

1
Gilad Naaman

Java semble avoir été conçu pour respecter une règle fondamentale selon laquelle == L'opérateur doit être légal chaque fois qu'un opérande peut être converti en type de l'autre, et doit comparer le résultat d'une telle conversion avec l'opérande non converti.

Cette règle n'est pas unique à Java, mais elle a des effets de grande envergure (et à mon humble avis) sur la conception d'autres aspects liés au type du langage. Il aurait été plus simple de spécifier les comportements de == en ce qui concerne des combinaisons particulières de types d'opérandes et interdire les combinaisons de types X et Y où x1==y1 et x2==y1 n'impliquerait pas x1==x2, mais les langues font rarement cela [selon cette philosophie, double1 == long1 devrait soit indiquer si double1 n'est pas une représentation exacte de long1, ou bien refuser de compiler; int1==Integer1 devrait être interdit, mais il devrait y avoir un moyen pratique et efficace de ne pas lancer pour tester si un objet est un entier encadré avec une valeur particulière (la comparaison avec quelque chose qui n'est pas un entier encadré devrait simplement retourner false )].

En ce qui concerne l'application du == opérateur aux chaînes, si Java avait interdit les comparaisons directes entre les opérandes de type String et Object, cela aurait pu éviter les surprises dans le comportement de ==, mais il n'y a aucun comportement qu'il puisse implémenter pour de telles comparaisons qui ne serait pas étonnant. Avoir deux références de chaîne conservées dans le type Object se comporterait différemment des références conservées dans le type String aurait été beaucoup moins étonnant que l'un ou l'autre de ces comportements diffère de celui d'une comparaison légale de type mixte. Si String1==Object1 est légal, cela impliquerait que la seule façon pour les comportements de String1==String2 et Object1==Object2 correspondre String1==Object1 serait pour eux de correspondre.

0
supercat

En général, il y a de très bonnes raisons de vouloir pouvoir tester si deux références d'objet pointent vers le même objet. J'ai eu plein de fois où j'ai écrit

Address oldAddress;
Address newAddress;
... populate values ...
if (oldAddress==newAddress)
... etc ...

Je peux ou non avoir une fonction égale dans de tels cas. Si je le fais, la fonction égale peut comparer le contenu entier des deux objets. Souvent, il compare simplement un identifiant. "A et B sont des références au même objet" et "A et B sont deux objets différents avec le même contenu" sont, bien sûr, deux idées très différentes.

Il est probablement vrai que pour les objets immuables, comme les chaînes, c'est moins un problème. Avec les objets immuables, nous avons tendance à penser que l'objet et la valeur sont la même chose. Eh bien, quand je dis "nous", je veux dire "je", au moins.

Integer three=new Integer(3);
Integer triangle=new Integer(3);
if (three==triangle) ...

Bien sûr, cela retourne faux, mais je peux voir quelqu'un penser que cela devrait être vrai.

Mais une fois que vous dites que == compare les poignées de référence et non le contenu des objets en général, faire un cas spécial pour les chaînes serait potentiellement déroutant. Comme quelqu'un d'autre l'a dit ici, que se passe-t-il si vous souhaitez comparer les poignées de deux objets String? Y aurait-il une fonction spéciale pour le faire uniquement pour les chaînes?

Et à propos de ...

Object x=new String("foo");
Object y=new String("foo");
if (x==y) ...

Est-ce faux parce que ce sont deux objets différents, ou vrai parce que ce sont des chaînes dont le contenu est égal?

Alors oui, je comprends comment les programmeurs sont confus. Je l'ai fait moi-même, je veux dire écrire si myString == "foo" quand je voulais dire si myString.equals ("foo"). Mais à moins de repenser la signification de l'opérateur == pour tous les objets, je ne vois pas comment y faire face.

0
Jay

C'est une question valable pour Strings, et pas seulement pour les chaînes, mais aussi pour d'autres objets immuables représentant une certaine "valeur", par exemple Double, BigInteger et même InetAddress.

Pour rendre l'opérateur == Utilisable avec des chaînes et d'autres classes de valeur, je vois trois alternatives:

  • Demandez au compilateur de connaître toutes ces classes de valeurs et la façon de comparer leur contenu. Si ce n'était qu'une poignée de classes du package Java.lang, Je considérerais cela, mais cela ne couvre pas les cas comme InetAddress.

  • Autorise la surcharge de l'opérateur afin qu'une classe définisse son comportement de comparaison ==.

  • Supprimez les constructeurs publics et disposez de méthodes statiques renvoyant des instances d'un pool, renvoyant toujours la même instance pour la même valeur. Pour éviter les fuites de mémoire, vous avez besoin de quelque chose comme SoftReferences dans le pool, qui n'existait pas dans Java 1.0. Et maintenant, pour maintenir la compatibilité, les constructeurs String() peuvent ' t être supprimé plus.

La seule chose qui pourrait encore être faite aujourd'hui serait d'introduire une surcharge d'opérateur, et personnellement je n'aime pas Java pour emprunter cette voie).

Pour moi, la lisibilité du code est la plus importante, et un programmeur Java sait que les opérateurs ont une signification fixe, définie dans la spécification du langage, tandis que les méthodes sont définies par du code, et leur signification doit être recherché dans le Javadoc de la méthode. J'aimerais garder cette distinction même si cela signifie que les comparaisons de chaînes ne pourront pas utiliser l'opérateur ==.

Il n'y a qu'un aspect des comparaisons de Java qui me dérange: l'effet de l'auto-boxing et du -unboxing. Il masque la distinction entre le type primitif et le type wrapper. Mais lorsque vous les comparez avec ==, Ils sont TRÈS différents.

    int i=123456;
    Integer j=123456;
    Integer k=123456;
    System.out.println(i==j);  // true or false? Do you know without reading the specs?
    System.out.println(j==k);  // true or false? Do you know without reading the specs?
0
Ralf Kleberhoff