Pourquoi Java n’offre-t-il pas une surcharge de l’opérateur?

En passant de C ++ à Java, la question sans réponse est la suivante: pourquoi Java n’a-t-il pas inclus la surcharge des opérateurs?

N’est-ce pas Complex a, b, c; a = b + c; Complex a, b, c; a = b + c; beaucoup plus simple que Complex a, b, c; a = b.add(c); Complex a, b, c; a = b.add(c); ?

Y a-t-il une raison connue à cela, des arguments valables pour ne pas autoriser la surcharge de l’opérateur? La raison est-elle arbitraire ou perdue dans le temps?

En supposant que vous vouliez écraser la valeur précédente de l’object référencé par a , une fonction membre devrait être appelée.

 Complex a, b, c; // ... a = b.add(c); 

Dans C ++, cette expression indique au compilateur de créer trois (3) objects sur la stack, d’effectuer des ajouts et de copier la valeur résultante de l’object temporaire dans l’object existant a .

Toutefois, en Java, operator= n’effectue pas de copie de valeur pour les types de référence, et les utilisateurs peuvent uniquement créer de nouveaux types de référence, pas des types de valeur. Donc, pour un type défini par l’utilisateur nommé Complex , l’affectation signifie copier une référence à une valeur existante.

Considérez plutôt:

 b.set(1, 0); // initialize to real number '1' a = b; b.set(2, 0); assert( !a.equals(b) ); 

En C ++, cela copie la valeur, de sorte que la comparaison ne sera pas égale. En Java, operator= exécute la copie de référence, donc a et b font maintenant référence à la même valeur. Par conséquent, la comparaison produira «égal», car l’object sera égal à lui-même.

La différence entre les copies et les références ne fait qu’append à la confusion de la surcharge de l’opérateur. Comme @Sebastian l’a mentionné, Java et C # doivent tous deux traiter séparément l’égalité de valeur et de référence – operator+ traiterait probablement les valeurs et les objects, mais operator= est déjà implémenté pour traiter les références.

En C ++, vous ne devriez avoir affaire qu’à un seul type de comparaison à la fois, cela peut donc être moins déroutant. Par exemple, sur Complex , operator= et operator== travaillent tous deux sur des valeurs – en copiant les valeurs et en comparant les valeurs respectivement.

Il y a beaucoup de messages qui se plaignent de la surcharge des opérateurs.

J’ai senti que je devais clarifier les concepts de “surcharge de l’opérateur”, offrant un sharepoint vue alternatif sur ce concept.

Code obscurcissant?

Cet argument est une erreur.

Le brouillage est possible dans toutes les langues …

Il est aussi facile de masquer le code en C ou Java en passant par les fonctions / méthodes qu’en C ++ par les surcharges des opérateurs:

 // C++ T operator + (const T & a, const T & b) // add ? { T c ; c.value = a.value - b.value ; // subtract !!! return c ; } // Java static T add (T a, T b) // add ? { T c = new T() ; c.value = a.value - b.value ; // subtract !!! return c ; } /* C */ T add (T a, T b) /* add ? */ { T c ; c.value = a.value - b.value ; /* subtract !!! */ return c ; } 

… Même dans les interfaces standard de Java

Pour un autre exemple, voyons l’ interface Cloneable sous Java:

Vous êtes censé cloner l’object implémentant cette interface. Mais tu pourrais mentir. Et créer un object différent. En fait, cette interface est si faible que vous pourriez retourner un autre type d’object, juste pour le plaisir:

 class MySincereHandShake implements Cloneable { public Object clone() { return new MyVengefulKickInYourHead() ; } } 

Comme l’interface Cloneable peut être abusée / masquée, devrait-elle être bannie pour les mêmes raisons que la surcharge de l’opérateur C ++?

Nous pourrions surcharger la toSsortingng() d’une classe MyComplexNumber pour qu’elle renvoie l’heure de la journée. La surcharge de toSsortingng() devrait elle aussi être bannie? On pourrait saboter MyComplexNumber.equals pour qu’il retourne une valeur aléatoire, modifie les opérandes … etc. etc.

En Java, comme en C ++ ou dans n’importe quel langage, le programmeur doit respecter un minimum de sémantique lors de l’écriture du code. Cela signifie implémenter une fonction add qui ajoute, et la méthode d’implémentation Cloneable qui clone, et un opérateur ++ que des incréments.

Qu’est-ce qui obscurcit quand même?

Maintenant que nous soaps que le code peut être saboté même avec les méthodes Java immaculées, nous pouvons nous interroger sur l’utilisation réelle de la surcharge d’opérateur en C ++?

Notation claire et naturelle: méthodes vs surcharge de l’opérateur?

Nous comparerons ci-dessous, pour différents cas, le “même” code en Java et en C ++, pour avoir une idée du type de style de codage le plus clair.

Comparaisons naturelles:

 // C++ comparison for built-ins and user-defined types bool isEqual = A == B ; bool isNotEqual = A != B ; bool isLesser = A < B ; bool isLesserOrEqual = A <= B ; // Java comparison for user-defined types boolean isEqual = A.equals(B) ; boolean isNotEqual = ! A.equals(B) ; boolean isLesser = A.comparesTo(B) < 0 ; boolean isLesserOrEqual = A.comparesTo(B) <= 0 ; 

Veuillez noter que A et B peuvent être de tout type en C ++, à condition que les surcharges de l'opérateur soient fournies. En Java, lorsque A et B ne sont pas des primitives, le code peut devenir très déroutant, même pour des objects de type primitif (BigInteger, etc.) ...

Accessoire de tableau naturel / conteneur et indice:

 // C++ container accessors, more natural value = myArray[25] ; // subscript operator value = myVector[25] ; // subscript operator value = mySsortingng[25] ; // subscript operator value = myMap["25"] ; // subscript operator myArray[25] = value ; // subscript operator myVector[25] = value ; // subscript operator mySsortingng[25] = value ; // subscript operator myMap["25"] = value ; // subscript operator // Java container accessors, each one has its special notation value = myArray[25] ; // subscript operator value = myVector.get(25) ; // method get value = mySsortingng.charAt(25) ; // method charAt value = myMap.get("25") ; // method get myArray[25] = value ; // subscript operator myVector.set(25, value) ; // method set myMap.put("25", value) ; // method put 

En Java, nous voyons que pour que chaque conteneur fasse la même chose (accéder à son contenu via un index ou un identifiant), nous avons une manière différente de le faire, ce qui est déroutant.

En C ++, chaque conteneur utilise le même moyen pour accéder à son contenu, grâce à la surcharge de l'opérateur.

Manipulation de types avancés naturels

Les exemples ci-dessous utilisent un object Masortingx , trouvé en utilisant les premiers liens trouvés sur Google pour " Objet Java Masortingx " et " Objet Masortingx c ++ ":

 // C++ YMasortingx masortingx implementation on CodeProject // http://www.codeproject.com/KB/architecture/ymasortingx.aspx // A, B, C, D, E, F are Masortingx objects; E = A * (B / 2) ; E += (A - B) * (C + D) ; F = E ; // deep copy of the masortingx // Java JAMA masortingx implementation (seriously...) // http://math.nist.gov/javanumerics/jama/doc/ // A, B, C, D, E, F are Masortingx objects; E = A.times(B.times(0.5)) ; E.plusEquals(A.minus(B).times(C.plus(D))) ; F = E.copy() ; // deep copy of the masortingx 

Et cela ne se limite pas aux masortingces. Les classes BigInteger et BigDecimal de Java souffrent de la même verbosité confuse, alors que leurs équivalents en C ++ sont aussi clairs que les types intégrés.

Itérateurs naturels:

 // C++ Random Access iterators ++it ; // move to the next item --it ; // move to the previous item it += 5 ; // move to the next 5th item (random access) value = *it ; // gets the value of the current item *it = 3.1415 ; // sets the value 3.1415 to the current item (*it).foo() ; // call method foo() of the current item // Java ListIterator "bi-directional" iterators value = it.next() ; // move to the next item & return the value value = it.previous() ; // move to the previous item & return the value it.set(3.1415) ; // sets the value 3.1415 to the current item 

Foncteurs naturels:

 // C++ Functors myFunctorObject("Hello World", 42) ; // Java Functors ??? myFunctorObject.execute("Hello World", 42) ; 

Concaténation de texte:

 // C++ stream handling (with the < < operator) stringStream << "Hello " << 25 << " World" ; fileStream << "Hello " << 25 << " World" ; outputStream << "Hello " << 25 << " World" ; networkStream << "Hello " << 25 << " World" ; anythingThatOverloadsShiftOperator << "Hello " << 25 << " World" ; // Java concatenation myStringBuffer.append("Hello ").append(25).append(" World") ; 

Ok, en Java, vous pouvez utiliser MySsortingng = "Hello " + 25 + " World" ; aussi ... Mais attendez une seconde: c'est la surcharge de l'opérateur, n'est-ce pas? N'est-ce pas sortingcher ???

:-RÉ

Code générique?

Les mêmes opérandes génériques de modification de code devraient être utilisables à la fois pour les primitifs intégrés (qui n'ont pas d'interface en Java), les objects standard (qui ne peuvent pas avoir l'interface correcte) et les objects définis par l'utilisateur.

Par exemple, calculer la valeur moyenne de deux valeurs de types arbitraires:

 // C++ primitive/advanced types template T getAverage(const T & p_lhs, const T & p_rhs) { return (p_lhs + p_rhs) / 2 ; } int intValue = getAverage(25, 42) ; double doubleValue = getAverage(25.25, 42.42) ; complex complexValue = getAverage(cA, cB) ; // cA, cB are complex Masortingx masortingxValue = getAverage(mA, mB) ; // mA, mB are Masortingx // Java primitive/advanced types // It won't really work in Java, even with generics. Sorry. 

Discuter de la surcharge de l'opérateur

Maintenant que nous avons vu des comparaisons justes entre le code C ++ utilisant la surcharge des opérateurs et le même code en Java, nous pouvons maintenant discuter de la "surcharge des opérateurs" en tant que concept.

La surcharge de l'opérateur existait depuis avant les ordinateurs

Même en dehors de l'informatique, les opérateurs sont surchargés: par exemple, en mathématiques, les opérateurs tels que + , - , * , etc. sont surchargés.

En effet, la signification de + , - , * , etc. change en fonction des types d'opérandes (numériques, vecteurs, fonctions d'onde quantique, masortingces, etc.).

La plupart d’entre nous, dans le cadre de nos cours de sciences, ont appris de multiples significations pour les opérateurs, en fonction des types d’opérandes. Les avons-nous trouvés confus, eux?

La surcharge de l'opérateur dépend de ses opérandes

C'est la partie la plus importante de la surcharge d'opérateur: comme en mathématiques ou en physique, l'opération dépend des types d'opérandes.

Donc, connaissez le type de l'opérande et vous connaîtrez l'effet de l'opération.

Même C et Java ont une surcharge de l'opérateur (codée en dur)

En C, le comportement réel d'un opérateur changera en fonction de ses opérandes. Par exemple, l'ajout de deux entiers est différent de l'ajout de deux doubles, voire d'un entier et d'un double. Il y a même tout le domaine arithmétique du pointeur (sans transtypage, vous pouvez append à un pointeur un entier, mais vous ne pouvez pas append deux pointeurs ...).

En Java, il n'y a pas d'arithmétique de pointeur, mais quelqu'un a toujours trouvé que la concaténation de chaînes sans l'opérateur + serait suffisamment ridicule pour justifier une exception dans le credo "La surcharge de l'opérateur est mauvaise".

C'est juste que vous, en tant que codeur C (pour des raisons historiques) ou Java (pour des raisons personnelles , voir ci-dessous), vous ne pouvez pas fournir le vôtre.

En C ++, la surcharge de l'opérateur n'est pas facultative ...

En C ++, la surcharge d'opérateur pour les types intégrés n'est pas possible (et c'est une bonne chose), mais les types définis par l' utilisateur peuvent avoir des surcharges d'opérateur définies par l'utilisateur .

Comme déjà dit précédemment, en C ++, et contrairement à Java, les types d'utilisateurs ne sont pas considérés comme des citoyens de seconde classe du langage, par rapport aux types intégrés. Ainsi, si les types intégrés ont des opérateurs, les types d'utilisateurs devraient également pouvoir les avoir.

La vérité est que, comme les méthodes toSsortingng() , clone() , equals() sont pour Java ( quasi-standard ), la surcharge d'opérateur C ++ fait tellement partie de C ++ qu'elle devient aussi naturelle que les opérateurs C originaux , ou les méthodes Java mentionnées précédemment.

Combiné à la programmation de modèles, la surcharge de l'opérateur devient un modèle de conception bien connu. En fait, vous ne pouvez pas aller très loin dans STL sans utiliser des opérateurs surchargés et surcharger les opérateurs pour votre propre classe.

... mais il ne faut pas en abuser

La surcharge de l'opérateur doit s'efforcer de respecter la sémantique de l'opérateur. Ne pas soustraire dans un opérateur + (comme dans "ne pas soustraire dans une fonction add ", ou "retourner la méthode dans une méthode clone ").

La surcharge de la casting peut être très dangereuse car elle peut entraîner des ambiguïtés. Ils devraient donc être vraiment réservés aux cas bien définis. Comme pour && et || , ne les surchargez jamais à moins que vous ne sachiez vraiment ce que vous faites, car vous perdrez l’évaluation en court-circuit que les opérateurs natifs && et || prendre plaisir.

Alors ... Ok ... Alors pourquoi ce n'est pas possible en Java?

Parce que James Gosling l'a dit:

J'ai laissé de côté la surcharge des opérateurs comme un choix assez personnel car j'avais vu trop de personnes en abuser en C ++.

James Gosling. Source: http://www.gotw.ca/publications/c_family_interview.htm

S'il vous plaît comparer le texte de Gosling ci-dessus avec Stroustrup ci-dessous:

Beaucoup de décisions en matière de conception du C ++ ont leurs racines dans mon aversion pour forcer les gens à faire les choses d'une manière particulière [...] Souvent, j'étais tenté d'interdire une fonctionnalité que je n'aimais pas personnellement. le droit de forcer mes opinions sur les autres .

Bjarne Stroustrup. Source: La conception et l'évolution du C ++ (1.3 Contexte général)

La surcharge de l'opérateur profiterait-elle à Java?

Certains objects bénéficieraient grandement de la surcharge des opérateurs (types concrets ou numériques, tels que BigDecimal, nombres complexes, masortingces, conteneurs, iterators, comparateurs, parsingurs, etc.).

En C ++, vous pouvez profiter de cet avantage grâce à l'humilité de Stroustrup. En Java, vous êtes simplement vexé par le choix personnel de Gosling.

Pourrait-il être ajouté à Java?

Les raisons pour ne pas append la surcharge des opérateurs en Java pourraient être un mélange de politique interne, d’allergie à la fonctionnalité, de méfiance des développeurs (vous savez, les saboteurs qui semblent hanter les équipes Java…), de compatibilité avec les précédentes JVM, le temps d'écrire une spécification correcte, etc.

Alors, ne retenez pas votre souffle en attendant cette fonctionnalité ...

Mais ils le font en C # !!!

Ouais...

Bien que ce soit loin d'être la seule différence entre les deux langues, celle-ci ne manque jamais de m'amuser.

Apparemment, les gens de C #, avec leur "chaque primitive est une struct , et une struct dérivée de Object" , ont tout de suite eu raison.

Et ils le font dans d' autres langues !!!

Malgré tous les FUD contre les surcharges d’opérateurs définies, les langages suivants le supportent: Scala , Dart , Python , F # , C # , D , Algol 68 , Smalltalk , Groovy , Perl 6 , C ++, Ruby , Haskell , MATLAB , Eiffel , Lua . Clojure , Fortran 90 , Swift , Ada , Delphi 2005 ...

Tant de langues, avec tant de philosophies différentes (et parfois opposées), et pourtant elles sont toutes d’accord sur ce point.

Nourriture pour la pensée...

James Gosling a comparé la conception de Java aux éléments suivants:

“Il y a ce principe du déménagement, quand on passe d’un appartement à un autre. Une expérience intéressante consiste à emballer votre appartement et à mettre tout dans des boîtes, puis à emménager dans l’appartement suivant sans rien déballer. vous faites votre premier repas et vous en sortez quelque chose puis, après environ un mois, vous vous êtes rendu compte de ce dont vous avez réellement besoin dans votre vie, puis vous prenez le rest du temps. stuff – oubliez combien vous l’aimez ou combien il est cool – et vous le jetez simplement C’est incroyable comment cela simplifie votre vie et vous pouvez utiliser ce principe dans toutes sortes de problèmes de conception: ne faites pas les choses simplement parce qu’elles Je suis cool ou juste parce qu’ils sont intéressants. ”

Vous pouvez lire le contexte de la citation ici

Fondamentalement, la surcharge de l’opérateur est idéale pour une classe qui modélise un certain type de point, de monnaie ou de nombre complexe. Mais après cela, vous commencez à manquer d’exemples rapidement.

Un autre facteur était l’abus de la fonctionnalité en C ++ par les développeurs qui surchargeaient des opérateurs tels que «&&», «||», les opérateurs de la dissortingbution et, bien sûr, les «nouveaux». La complexité résultant de la combinaison de cette méthode avec la valeur par défaut et les exceptions est bien couverte dans le livre C ++ exceptionnel .

Découvrez Boost.Units: texte du lien

Il fournit une parsing dimensionnelle sans surcharge par surcharge de l’opérateur. Comment cela peut-il être plus clair?

 quantity F = 2.0*newton; quantity dx = 2.0*meter; quantity E = F * dx; std::cout < < "Energy = " << E << endl; 

serait en fait sortie "Energy = 4 J" qui est correct.

Les concepteurs de Java ont décidé que la surcharge de l’opérateur posait plus de problèmes qu’elle n’en valait la peine. Aussi simple que cela.

Dans un langage où chaque variable d’object est en réalité une référence, la surcharge d’opérateur présente le risque supplémentaire d’être tout à fait illogique – au moins pour un programmeur C ++. Comparez la situation avec la surcharge de l’opérateur Object.Equals Object.ReferenceEquals Object.Equals et Object.ReferenceEquals et Object.ReferenceEquals (ou peu importe Object.ReferenceEquals elle s’appelle).

Groovy a une surcharge d’opérateur et s’exécute dans la JVM. Si cela ne vous dérange pas la performance (qui diminue chaque jour). C’est automatique en fonction des noms de méthodes. Par exemple, ‘+’ appelle la méthode ‘plus (argument)’.

Je pense que cela peut avoir été un choix conscient de la conception pour forcer les développeurs à créer des fonctions dont les noms communiquent clairement leurs intentions. En C ++, les développeurs surchargeraient les opérateurs avec des fonctionnalités qui n’auraient souvent aucune relation avec la nature communément acceptée de l’opérateur donné, ce qui rend presque impossible de déterminer ce que fait un morceau de code sans examiner la définition de l’opérateur.

Eh bien, vous pouvez vraiment vous tirer dans le pied avec une surcharge de l’opérateur. C’est comme avec les pointeurs, les gens font des erreurs stupides avec eux et il a donc été décidé de retirer les ciseaux.

Au moins je pense que c’est la raison. Je suis de ton côté quand même. 🙂

Dire que la surcharge d’opérateur conduit à des erreurs logiques de type que l’opérateur ne correspond pas à la logique d’opération, c’est comme ne rien dire. Le même type d’erreur se produira si le nom de la fonction est inapproprié pour la logique d’opération – alors quelle est la solution: supprimez la possibilité d’utilisation de la fonction !? Ceci est une réponse comique – “inapproprié pour la logique de fonctionnement”, chaque nom de paramètre, chaque classe, fonction ou autre peut être logique. Je pense que cette option devrait être disponible dans un langage de programmation respectable, et que ceux qui pensent que ce n’est pas sûr – il n’y a pas de quoi dire que vous devez l’utiliser. Prenons le C #. Ils ont affaissé les pointeurs mais bon – il y a une déclaration de «code dangereux» – programmez comme vous le souhaitez à vos risques et périls.

Techniquement, il y a une surcharge de l’opérateur dans chaque langage de programmation qui peut traiter différents types de nombres, par exemple des nombres entiers et des nombres réels. Explication: Le terme surcharge signifie qu’il existe simplement plusieurs implémentations pour une fonction. Dans la plupart des langages de programmation, différentes implémentations sont fournies pour l’opérateur +, une pour les entiers, une pour les réels, cela s’appelle la surcharge de l’opérateur.

Maintenant, beaucoup de gens trouvent étrange que Java ait une surcharge d’opérateur pour l’opérateur + pour append des chaînes, et d’un sharepoint vue mathématique, ce serait étrange mais vu le langage de développement d’un développeur, il n’y a rien de mal à append une surcharge opérateur pour l’opérateur + pour les autres classes, par exemple Ssortingng. Cependant, la plupart des gens s’accordent à dire qu’une fois la surcharge intégrée pour + pour Ssortingng, il est généralement recommandé de fournir cette fonctionnalité au développeur.

Tout à fait en désaccord avec l’erreur sur le fait que la surcharge de l’opérateur brouille le code, car c’est au développeur de décider. C’est naïf de penser, et pour être tout à fait honnête, ça vieillit.

+1 pour l’ajout de la surcharge d’opérateur dans Java 8.

Certaines personnes disent que la surcharge des opérateurs en Java conduirait à l’obscurcissement. Ces personnes se sont-elles déjà arrêtées pour regarder du code Java en effectuant des calculs de base, comme l’augmentation d’une valeur financière d’un pourcentage en utilisant BigDecimal? …. la verbosité d’un tel exercice devient sa propre démonstration d’obsolescence. Ironically, adding operator overloading to Java would allow us to create our own Currency class which would make such mathematical code elegant and simple (less obsfuscated).

Assuming Java as the implementation language then a, b, and c would all be references to type Complex with initial values of null. Also assuming that Complex is immutable as the mentioned BigInteger and similar immutable BigDecimal , I’d I think you mean the following, as you’re assigning the reference to the Complex returned from adding b and c, and not comparing this reference to a.

Isn’t :

 Complex a, b, c; a = b + c; 

much simpler than:

 Complex a, b, c; a = b.add(c); 

Sometimes it would be nice to have operator overloading, friend classes and multiple inheritance.

However I still think it was a good decision. If Java would have had operator overloading then we could never be sure of operator meanings without looking through source code. At present that’s not necessary. And I think your example of using methods instead of operator overloading is also quite readable. If you want to make things more clear you could always add a comment above hairy statements.

 // a = b + c Complex a, b, c; a = b.add(c); 

This is not a good reason to disallow it but a practical one:

People do not always use it responsibly. Look at this example from the Python library scapy:

 >>> IP()  >>> IP()/TCP() > >>> Ether()/IP()/TCP() >> >>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n" >> >>> Ether()/IP()/IP()/UDP() >>> >>> IP(proto=55)/TCP() > 

Here is the explanation:

The / operator has been used as a composition operator between two layers. When doing so, the lower layer can have one or more of its defaults fields overloaded according to the upper layer. (You still can give the value you want). A ssortingng can be used as a raw layer.

Alternatives to Native Support of Java Operator Overloading

Since Java doesn’t have operator overloading, here are some alternatives you can look into:

  1. Use another language. Both Groovy and Scala have operator overloading, and are based on Java.
  2. Use java-oo , a plugin that enables operator overloading in Java. Note that it is NOT platform independent. Also, it has many issues, and is not compatible with the latest releases of Java (ie Java 10). ( Original StackOverflow Source )
  3. Use JNI , Java Native Interface, or alternatives. This allows you to write C or C++ (maybe others?) methods for use in Java. Of course this is also NOT platform independent.

If anyone is aware of others, please comment, and I will add it to this list.