Pourquoi Java n'offre-t-il pas de surcharge aux opérateurs?

407

En venant de C ++ à Java, la question évidente sans réponse est pourquoi Java n'a-t-il pas inclus la surcharge de l'opérateur?

N'est-ce pas Complex a, b, c; a = b + c;beaucoup plus simple que 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?

rengolin
la source
1
@zzzz, j'ai du mal à lire cet article. Cette traduction a-t-elle été effectuée automatiquement ou l'anglais est-il la deuxième langue de l'auteur? Je trouve que la discussion ici est beaucoup plus nette.
25
Pour les tas de gens qui concluent ceci comme non constructif, cette question a donné lieu au dialogue le plus constructif que j'ai vu à SO. C'est peut-être un meilleur candidat pour programmers.stackexchange.com , mais il y a des moments où je pense que SO est trop méprisant pour des sujets plus larges.
@NoNaMe c'est facile, il suffit d'insérer mentalement un et le - les artlcles manquants indiquent que la personne n'est pas un locuteur natif anglais ou un programmeur (ou comme ce type, les deux :) La raison pour laquelle les programmeurs peuvent déposer des articles est qu'il peut faites des commentaires plus courts et rentrez plus facilement dans l'espace prévu. à partir de là, ils s'y habituent. Mon problème est avec la mise en page, d'une manière ou d'une autre, je frappe toujours ce site dans les recherches Google. Heureusement, il existe une excellente extension chromée appelée Clearly qui reformate à merveille les pages difficiles à lire.
ycomp
1
Je ne vois aucune raison pour laquelle et comment OP a accepté la première réponse? La réponse écrite par @ stackoverflow.com/users/14089/paercebal est excellente. Cela devrait être accepté.
Destructor

Réponses:

13

En supposant que vous vouliez remplacer la valeur précédente de l'objet référencé par a, alors une fonction membre devrait être invoquée.

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

En C ++, cette expression indique au compilateur de créer trois (3) objets sur la pile, d'effectuer l'ajout et de copier la valeur résultante de l'objet temporaire dans l'objet existant a.

Cependant, 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. Ainsi, pour un type défini par l'utilisateur nommé Complex, l'affectation signifie copier une référence vers 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) ); // this assertion will fail

En C ++, cela copie la valeur, donc la comparaison ne sera pas égale. En Java, operator=effectue une copie de référence, donc aet bfait maintenant référence à la même valeur. En conséquence, la comparaison produira «égal», puisque l'objet se comparera égal à lui-même.

La différence entre les copies et les références ne fait qu'ajouter à 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é des valeurs et des références - operator+traiteraient probablement des valeurs et des objets, mais operator=sont déjà implémentés pour gérer les références.

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

Aaron
la source
6
C'est assez simple vraiment ... Faites comme Python et n'avez pas d'affectation surchargée.
L̲̳o̲̳̳n̲̳̳g̲̳̳p̲̳o̲̳̳k̲̳̳e̲̳̳
225
Cette réponse ne répond pas du tout à la question. Vous harpez simplement sur l'utilisation par java du signe égal. Si b + C renvoyait un nouveau complexe, alors a = b + c serait parfaitement valide, et oui beaucoup plus simple à lire. Même si vous vouliez modifier un sur place, a.set (b + c) est une tonne plus simple à lire - surtout lorsque l'arithmétique est plus que triviale: a.set ((a b + b c) / 5) ou a = a. multiplier (b) .add (b. multiplier (c)). diviser (5). Votre choix ..
BT
24
Ou je suppose .. pas votre choix, selon le cas
BT
9
En C ++, les modèles d'expression résolvent le problème de la copie supplémentaire. Presque toutes les grandes bibliothèques arithmétiques utilisent cette technique pour cette raison. De plus, cela ne répond pas à la question, car a = b + c n'est que du sucre syntaxique pour a.foo (b.bar (c)), qui est vraiment l'observation initiale de la question.
Kaz Dragon
18
Ce n'est pas la réponse à la question posée. Ce sont les spéculations de quelqu'un sur certaines différences entre Java et C ++.
SChepurin
805

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», en offrant un point de vue alternatif sur ce concept.

Code obscurcissant?

Cet argument est une erreur.

L'obscurcissement est possible dans toutes les langues ...

Il est aussi facile d'obscurcir le code en C ou Java via des fonctions / méthodes qu'en C ++ via des surcharges d'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' Cloneableinterface en Java:

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

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

Comme le Cloneable interface peut être utilisée abusivement / obscurcie, devrait-elle être interdite pour les mêmes motifs que la surcharge de l'opérateur C ++ est censée être?

Nous pourrions surcharger la toString()méthode d'une MyComplexNumberclasse pour qu'elle retourne l'heure stringifiée de la journée. La toString()surcharge devrait-elle également être interdite? On pourrait saboter MyComplexNumber.equalspour lui faire retourner une valeur aléatoire, modifier les opérandes ... etc 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 de code. Cela signifie implémenter une addfonction qui ajoute, et une Cloneableméthode d'implémentation qui clone, et un++ opérateur qui incrémente.

Qu'est-ce qui obscurcit de toute façon?

Maintenant que nous savons que le code peut être saboté même à travers 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 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 ++, tant que les surcharges d'opérateur sont fournies. En Java, lorsque A et B ne sont pas des primitives, le code peut devenir très déroutant, même pour des objets de type primitif (BigInteger, etc.) ...

Accesseurs naturels de tableau / conteneur et indice:

// C++ container accessors, more natural
value        = myArray[25] ;         // subscript operator
value        = myVector[25] ;        // subscript operator
value        = myString[25] ;        // subscript operator
value        = myMap["25"] ;         // subscript operator
myArray[25]  = value ;               // subscript operator
myVector[25] = value ;               // subscript operator
myString[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        = myString.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 de la même manière pour accéder à son contenu, grâce à la surcharge de l'opérateur.

Manipulation naturelle des types avancés

Les exemples ci-dessous utilisent un Matrixobjet, trouvé en utilisant les premiers liens trouvés sur Google pour " objet Matrix Java " et " objet Matrix C ++ ":

// C++ YMatrix matrix implementation on CodeProject
// http://www.codeproject.com/KB/architecture/ymatrix.aspx
// A, B, C, D, E, F are Matrix objects;
E =  A * (B / 2) ;
E += (A - B) * (C + D) ;
F =  E ;                  // deep copy of the matrix

// Java JAMA matrix implementation (seriously...)
// http://math.nist.gov/javanumerics/jama/doc/
// A, B, C, D, E, F are Matrix objects;
E = A.times(B.times(0.5)) ;
E.plusEquals(A.minus(B).times(C.plus(D))) ;
F = E.copy() ;            // deep copy of the matrix

Et cela ne se limite pas aux matrices. Le BigIntegeretBigDecimal classes de Java souffrent de la même verbosité déroutante, tandis 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<E> "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 MyString = "Hello " + 25 + " World" ; aussi ... Mais, attendez une seconde: c'est la surcharge de l'opérateur, n'est-ce pas? N'est-ce pas de la triche ???

:-RÉ

Code générique?

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

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

// C++ primitive/advanced types
template<typename T>
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
Matrix  matrixValue  = getAverage(mA, mB) ; // mA, mB are Matrix

// 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 équitables entre le code C ++ utilisant la surcharge d'opérateur et le même code en Java, nous pouvons maintenant discuter de la "surcharge d'opérateur" en tant que concept.

La surcharge des opérateurs existait depuis avant les ordinateurs

Même en dehors de la science informatique, il y a surcharge de l' opérateur: Par exemple, en mathématiques, des opérateurs comme +, -, *, etc. sont surchargés.

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

La plupart d'entre nous, dans le cadre de nos cours de sciences, ont appris plusieurs significations pour les opérateurs, selon les types d'opérandes. Les avons-nous trouvés déroutants, eux?

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

C'est la partie la plus importante de la surcharge des opérateurs: comme en mathématiques ou en physique, l'opération dépend du type de ses 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 d'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, ou même d'un entier et d'un double. Il y a même tout le domaine arithmétique du pointeur (sans transtypage, vous pouvez ajouter à un pointeur un entier, mais vous ne pouvez pas ajouter deux pointeurs ...).

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

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 d'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 définis par l'utilisateur surcharges d'opérateur .

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. Donc, 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 toString(), clone(), les equals()méthodes sont pour Java ( par exemple quasi-standard comme ), la surcharge de l' opérateur de C est tellement partie de C ++ qu'il devient aussi naturel que les opérateurs d' origine C, ou avant des méthodes Java mentionnées.

Combinée à 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 des opérateurs de surcharge 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 addfonction", ou "renvoyer des conneries dans une cloneméthode").

La surcharge de plâtre peut être très dangereuse car elle peut conduire à des ambiguïtés. Ils devraient donc vraiment être réservés à des cas bien définis. En ce qui concerne &&et ||, ne savent jamais les surcharger , sauf si vous vraiment ce que vous faites, que vous perdrez l'évaluation de court-circuit que les opérateurs natifs &&et ||profiter.

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

Parce que James Gosling l'a dit:

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 ++.

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

Veuillez comparer le texte de Gosling ci-dessus avec Stroustrup ci-dessous:

De nombreuses décisions de conception C ++ ont leurs racines dans mon aversion pour forcer les gens à faire des choses d'une manière particulière [...] Souvent, j'ai été tenté de proscrire une fonctionnalité que je n'aimais pas personnellement, je me suis abstenu de le faire parce que je ne pensais pas que j'avais le droit de forcer mes vues sur les autres .

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

La surcharge de l'opérateur serait-elle avantageuse pour Java?

Certains objets bénéficieraient grandement de la surcharge des opérateurs (types concrets ou numériques, comme BigDecimal, nombres complexes, matrices, conteneurs, itérateurs, comparateurs, analyseurs, etc.).

En C ++, vous pouvez profiter de cet avantage en raison de l'humilité de Stroustrup. En Java, vous êtes simplement foutu à cause du choix personnel de Gosling .

Pourrait-il être ajouté à Java?

Les raisons de ne pas ajouter de surcharge d'opérateur maintenant à Java pourraient être un mélange de politique interne, d'allergie à la fonctionnalité, de méfiance envers les développeurs (vous savez, les saboteurs qui semblent hanter les équipes Java ...), la compatibilité avec les JVM précédentes, 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...

Si c'est loin d'être la seule différence entre les deux langues, celle-ci ne manque jamais de m'amuser.

Apparemment, les gens C #, avec leur "chaque primitive est un struct, et un structdérive d'Object" , ont bien fait du premier coup.

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

Malgré tous les FUD contre la surcharge d'opérateur définie utilisée, les langages suivants le prennent en charge: 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...

paercebal
la source
50
Ceci est une excellente réponse. Je ne suis pas d'accord, mais c'est toujours une excellente réponse. Je pense que les problèmes qui sont possibles avec de mauvaises surcharges dépassent la valeur des bonnes surcharges.
Douglas Leeder
69
@Douglas Leeder: Merci! La surcharge de l'opérateur est comme la POO. La première fois que vous apprenez à le faire, vous écrivez des surcharges partout comme vous mettriez les classes de base et l'héritage partout (comme, douce ironie, l'API Java). Mais cela passe assez vite et ensuite vous appréciez la possibilité tout en n'en abusant pas. Ma propre expérience de plus de 10 ans sur C ++ est que le nombre de surcharges incorrectes que j'ai vues à la fois dans mon code et dans le code d'autres codeurs est si faible que je pense pouvoir les compter d'une part. Et c'est beaucoup moins que le nombre de bogues globaux avec sprintf, strcat, memset et dépassements de tampon.
paercebal
11
@Douglas Leeder: Je pense, après en avoir discuté dans une autre question SO, que l'écart entre les "amoureux" et les "ennemis" de la surcharge des opérateurs est probablement dû à une différence d'approche du code: les "haineux" sont plus des "fonctions" sont ce qui compte ", c'est-à-dire qu'ils s'attendent à ce qu'une fonction fasse une chose, et une seule chose. Ainsi, les opérateurs devraient fonctionner comme prévu par le langage. Les «amoureux» sont davantage des «objets doivent se comporter», ce qui signifie qu'ils acceptent plus facilement que la fonction (et donc les opérateurs) peuvent changer leur comportement en fonction du type de leurs paramètres.
paercebal
103
Réponse épique. L'un des debunks les plus qualifiés que j'ai jamais lu.
Sebastian Mach
7
@MaartenBodewes: Tous les exemples que j'ai écrits ci-dessus, et tout ce qui vous dérange est le "en tant que développeur, vous êtes foutu parce que le choix personnel de Gosling" ? S'il vous plaît, écrivez votre propre réponse, en défendant l' angle "vous, les développeurs, êtes stupides, laissez les génies décider pour vous de ce dont vous avez besoin" . Cette discussion ne sert à rien.
paercebal
44

James Gosling a comparé la conception de Java à ce qui suit:

«Il y a ce principe à propos du déménagement, lorsque vous passez d'un appartement à un autre appartement. Une expérience intéressante consiste à emballer votre appartement et à tout mettre dans des boîtes, puis à emménager dans l'appartement suivant et à ne rien déballer jusqu'à ce que vous en ayez besoin. vous préparez votre premier repas, et vous sortez quelque chose d'une boîte. Ensuite, après un mois environ, vous l'avez utilisé pour comprendre à peu près ce dont vous avez réellement besoin dans votre vie, puis vous prenez le reste de la - oubliez à quel point vous l'aimez ou à quel point c'est cool - et vous le jetez simplement. C'est incroyable de voir comment cela vous simplifie la vie, et vous pouvez utiliser ce principe dans toutes sortes de problèmes de conception: ne faites pas les choses simplement parce qu'elles sont cool ou simplement 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 une sorte de point, de devise ou de nombre complexe. Mais après cela, vous commencez à manquer d'exemples rapidement.

Un autre facteur a été l'abus de la fonctionnalité en C ++ par des développeurs surchargeant des opérateurs comme '&&', '||', les opérateurs de cast et bien sûr 'new'. La complexité résultant de la combinaison de cela avec la valeur de passage et les exceptions est bien couverte dans le livre Exceptional C ++ .

Garth Gilmour
la source
6
Pourriez-vous fournir un exemple de code de "la complexité de la surcharge d'opérateur combinée à la valeur de passage et aux exceptions"? Malgré quelques années à jouer avec le langage, à posséder et à avoir lu tous les livres efficaces / exceptionnels sur C ++, je ne comprends pas ce que vous entendez par là.
paercebal
60
Ce qui fonctionne pour James Gosling ne fonctionnera pas pour tout le monde. Il est incroyablement myope pour avoir extrapolé son expérience d'emballage "intéressante" pour signifier "Jeter tout ce dont je n'ai pas besoin dans le monde, donc personne ne peut utiliser ce genre de choses." Il ne sait clairement pas ce dont j'ai besoin ou ce que j'utilise.
BT
49
@BT: La plupart enlightning est le point de vue de Gosling par rapport au point de vue de Stroustrup sur cette question: Many C++ design decisions have their roots in my dislike for forcing people to do things in some particular way [...] Often, I was tempted to outlaw a feature I personally disliked, I refrained from doing so because I did not think I had the right to force my views on others. (B. Stroustrup).
paercebal
29
@Software Monkey: "C ++, largement vilipendé par rapport à l'autre, Java, très apprécié" C'est du battage publicitaire. N'oubliez pas que C ++ a grandi seul, tandis que Java (et .NET) a profité de la commercialisation de bulldozers. N'est-il pas étrange que pour un "langage largement apprécié", Java soit limité aux applications serveur, alors que "largement vilipendé" (probablement par les développeurs et les gestionnaires Java voulant réduire le coût de production du code) C ++ passe de très haut- serveurs de performance aux jeux de haute performance? [...]
paercebal
16
@Hassan: Chaque langue a ses hacks, les génériques Java en sont un excellent exemple. Maintenant, à propos de I'd like them to go have a look at some C++ code out there that is hideously put together with weird hacks and "exceptional" features of the language: Les mauvais programmeurs écriront du mauvais code, peu importe la langue. Essayez juste d'émuler une "passe-par-référence" pour que les paramètres de fonction en Java aient une idée. J'ai vu le code et j'ai ri si fort que ça m'a fait mal. C'est le genre de choses que Gosling n'a pas utilisé, donc, il fallait des horribles hacks pour Java, pourtant, il existe nativement, à un coût nul, en C # et C ++.
paercebal
22

Découvrez Boost.Units: texte du lien

Il fournit une analyse dimensionnelle sans frais généraux grâce à une surcharge de l'opérateur. Jusqu'à quel point cela peut-il être plus clair?

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

produirait en fait "Energy = 4 J", ce qui est correct.

user15793
la source
1
"Comment exactement si complique la maintenance et où diable ce code obscurcit-il?"
Mooing Duck
13

Les concepteurs Java ont décidé que la surcharge des opérateurs était plus problématique qu'elle n'en valait la peine. Aussi simple que cela.

Dans un langage où chaque variable objet est en fait une référence, la surcharge d'opérateur présente le risque supplémentaire d'être tout à fait illogique - pour un programmeur C ++ au moins. Comparez la situation avec la surcharge de l'opérateur == de C # et Object.Equalset Object.ReferenceEquals(ou tout autre nom).

Sebastian Redl
la source
8

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 de jour en jour). Il est automatique en fonction des noms de méthode. par exemple, '+' appelle la méthode 'plus (argument)'.

Noé
la source
4
Je souhaite que tous les langages à syntaxe lourde avec surcharge d'opérateur aient utilisé cette technique. Je n'ai jamais compris pourquoi ils doivent inventer une version spéciale de la dénomination et de la recherche de méthode. Stroustrup ne mentionne aucune alternative dans D & EC ++. L'équipe C # a adopté la bonne approche avec la syntaxe Linq ( where ...devient .Where(i => ... ). Si seulement ils avaient fait de même avec les opérateurs arithmétiques, tant de choses seraient plus simples et plus puissantes. Java a l'avantage d'une table rase et pourrait bien faire les choses (bien que pour des raisons religieuses, il ne le sera probablement jamais).
Daniel Earwicker
@DanielEarwicker, j'ai souvent remarqué que lorsqu'il y a des désaccords compliqués, les gens marqueront les motivations de chaque camp comme étant de nature "religieuse".
@noah, je pourrais vivre avec un sous-ensemble limité de surcharge d'opérateur comme celui-ci, à condition qu'il y ait une balise spéciale dans les noms de méthode qui les maintiennent visuellement distincts. Quelque chose comme définir une méthode __plus () pour l'implémentation d'un "+" OL, et rester loin de surcharger des choses comme les transtypages et même les indices de tableau. Ce que je ne veux pas vivre, c'est la façon dont C ++ et C # ont jugé bon de l'implémenter.
2
Pas une réponse. Il existe de nombreuses langues en cours d'exécution sur la machine virtuelle. La surcharge des opérateurs ne devrait pas être en soi une bonne raison de changer de langue.
Maarten Bodewes
6

Je pense que cela a peut-être été un choix de conception conscient 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 de fonctionnalités qui n'auraient souvent aucun rapport avec la nature communément acceptée de l'opérateur donné, ce qui rend presque impossible de déterminer ce qu'un code fait sans regarder la définition de l'opérateur.

user14128
la source
14
In C++ developers would overload operators with functionality that would often have no relation to the commonly accepted nature of the given operator: Ceci est une affirmation gratuite. Je suis développeur C ++ professionnel depuis 12 ans et j'ai rarement rencontré ce problème. En fait, la plupart des bogues et des erreurs de conception que j'ai vus en C ++ se trouvaient dans le code de style C ( void *, transtypages, etc.)
paercebal
6
-1. Chaque variable que vous affectez est un symbole, tout comme les symboles d'opérateur arithmétique. Que vous utilisiez une expression pour nommer cette variable, un seul mot ou une seule lettre, c'est votre décision (ou celle de votre équipe). Qui doit dire ce qui est significatif et ce qui ne l'est pas? La réponse est vous, le programmeur. En mathématiques pures, la multiplication entre matrices signifie quelque chose de différent de la multiplication entre deux nombres en arithmétique de base. Pourtant, nous utilisons les mêmes symboles pour les deux types de multiplication.
Ingénieur
2
@paercebal: L'affirmation est malheureusement correcte. Vous ne devez pas regarder plus loin que IOstreams pour le voir en action. Heureusement, la plupart des développeurs sont plus circonspects quant à l'invention de nouvelles sémantiques pour les opérateurs existants.
Ben Voigt
5
@BenVoigt: [...] Et je ne mentionne même pas le fait que la addfonction pourrait être vraiment vraiment mal utilisée (comme faire une multiplication, ou acquérir un mutex) ... L'abus mentionné par user14128 n'est pas limité aux opérateurs, mais il y a une sorte de peur pathologique de la surcharge des opérateurs qui, je pense, vient des premiers jours de C contre C ++, une peur qui n'a pas été modifiée directement en Java, mais heureusement, n'est pas entrée en C # ... En fin de compte, en respectant la sémantique et écrire des fonctions / opérateurs clairs est le travail du développeur. Pas la langue.
paercebal
3
@ jbo5112: Exemple: cout << f() || g(); les parenthèses ne le rendent pas plus clair, elles le rendent correct. Et si les opérateurs de décalage de bits n'étaient pas abusés, ils ne seraient pas nécessaires. Pourquoi vaut cout << (5&3) << endl;mieux que cout.fmt(5&3)(endl);? L'utilisation de l'opérateur d'appel de fonction sur une variable membre du foncteur serait une conception infiniment meilleure pour les flux que la réorientation des opérateurs au niveau du bit simplement parce que le glyphe est joli. Mais c'est loin d'être le seul problème avec les streams.
Ben Voigt
5

Eh bien, vous pouvez vraiment vous tirer une balle 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é d'enlever les ciseaux.

Du moins, je pense que c'est la raison. Je suis de ton côté de toute façon. :)

Sarien
la source
Comme par exemple cette stupide erreur ...
Amir Kirsh
2
C'est une très mauvaise façon de penser. Vous pouvez vous tirer une balle dans le pied, nous vous coupons plutôt les mains, donc vous ne pourrez pas. Et bien sûr, nous supposons que vous êtes un idiot qui se suicidera.
ntj
5

Certaines personnes disent que la surcharge des opérateurs en Java conduirait à l'obscurcissement. Ces personnes se sont-elles jamais arrêtées pour regarder du code Java faisant des calculs de base comme augmenter une valeur financière d'un pourcentage en utilisant BigDecimal? .... la verbosité d'un tel exercice devient sa propre démonstration d'obscurcissement. Ironiquement, l'ajout d'une surcharge d'opérateur à Java nous permettrait de créer notre propre classe Currency, ce qui rendrait ce code mathématique élégant et simple (moins obscurci).

Volksman
la source
4

Dire que la surcharge de l'opérateur entraîne des erreurs logiques de type tel que l'opérateur ne correspond pas à la logique de fonctionnement, 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 de fonctionnement - alors quelle est la solution: abandonner la capacité d'utilisation de la fonction!? Ceci est une réponse comique - "inapproprié pour la logique d'opération", chaque nom de paramètre, chaque classe, fonction ou autre peut être logiquement inapproprié. Je pense que cette option devrait être disponible dans un langage de programmation respectable, et ceux qui pensent que ce n'est pas sûr - hé, aucun ne dit que vous devez l'utiliser. Prenons le C #. Ils ont baissé les pointeurs mais bon - il y a une déclaration de «code dangereux» - programmez comme vous le souhaitez à vos propres risques.

Kvant
la source
4

Techniquement, il y a une surcharge d'opérateur dans chaque langage de programmation qui peut traiter différents types de nombres, par exemple des nombres entiers et 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, c'est ce qu'on 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 ajouter des chaînes ensemble, et d'un point de vue mathématique, ce serait vraiment étrange, mais vu du point de vue du développeur d'un langage de programmation, il n'y a rien de mal à ajouter une surcharge d'opérateur intégrée pour l'opérateur + pour d'autres classes, par exemple String. Cependant, la plupart des gens conviennent qu'une fois que vous ajoutez une surcharge intégrée pour + pour String, il est généralement préférable de fournir également cette fonctionnalité au développeur.

Un désaccord complet avec l'erreur selon laquelle la surcharge de l'opérateur obscurcit le code, car c'est au développeur d'en décider. C'est naïf de penser, et pour être honnête, ça vieillit.

+1 pour l'ajout d'une surcharge d'opérateur dans Java 8.

Olai
la source
L'utilisation de Java +pour concaténer n'importe quoi de chaîne est à mon humble avis, tout comme la surcharge de /C et FORTRAN pour la division entière et fractionnée. Dans de nombreuses versions de Pascal, l'utilisation d'opérateurs arithmétiques sur n'importe quel type numérique produira des résultats numériquement équivalents à la conversion des opérandes Real, bien que les résultats qui pourraient ne pas être des nombres entiers doivent être transmis Truncou Roundavant qu'ils puissent être affectés à des entiers.
supercat
2

En supposant que Java soit le langage d'implémentation, alors a, b et c seraient tous des références au type Complex avec des valeurs initiales nulles. En supposant également que le complexe est immuable en tant que BigInteger mentionné et BigDecimal immuable similaire , je pense que vous voulez dire ce qui suit, car vous attribuez la référence au complexe renvoyé par l'ajout de b et c, et ne comparez pas cette référence à a.

N'est-ce pas:

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

beaucoup plus simple que:

Complex a, b, c; a = b.add(c);
David Schlosnagle
la source
2
Suis-je? ;) Égaux peut à la fois signifier affectation ou comparaison, mais = est toujours une affectation et == est toujours une comparaison. Les noms peuvent introduire eux-mêmes de grandes sources d'erreur.
1

Parfois, ce serait bien d'avoir une surcharge d'opérateur, des classes d'amis et un héritage multiple.

Cependant, je pense toujours que c'était une bonne décision. Si Java avait eu une surcharge d'opérateur, nous ne pourrions jamais être sûrs de la signification de l'opérateur sans regarder à travers le code source. Pour l'instant, ce n'est pas nécessaire. Et je pense que votre exemple d'utilisation de méthodes au lieu d'une surcharge d'opérateur est également assez lisible. Si vous voulez clarifier les choses, vous pouvez toujours ajouter un commentaire au-dessus des déclarations velues.

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

la source
12
Bien sûr, comme mentionné ailleurs, vous ne pouvez jamais non plus être sûr de la signification de la fonction add.
Eclipse
Certes, je trouve toujours réconfortant de savoir qu'au moins mes opérateurs sont codés en dur. Bien sûr, avoir les fonctionnalités et les utiliser judicieusement ne ferait que nous faire du bien. Le problème est qu'il est difficile de savoir si quelqu'un les a utilisées de manière raisonnable. Et que vous êtes d'accord sur la définition de sensiblement. :-)
1
Le commentaire ajouté pour clarifier le code, est à quoi le code ressemblerait dans un langage qui supportait la surcharge de l'opérateur. De plus, le fait que le commentaire soit écrit en termes d'opérateurs dément votre opposition à la surcharge des opérateurs.
Aluan Haddad du
0

Ce n'est pas une bonne raison de le refuser mais une raison pratique:

Les gens ne l'utilisent pas toujours de manière responsable. Regardez cet exemple de la scapy de la bibliothèque Python:

>>> IP()
<IP |>
>>> IP()/TCP()
<IP frag=0 proto=TCP |<TCP |>>
>>> Ether()/IP()/TCP()
<Ether type=0x800 |<IP frag=0 proto=TCP |<TCP |>>>
>>> IP()/TCP()/"GET / HTTP/1.0\r\n\r\n"
<IP frag=0 proto=TCP |<TCP |<Raw load='GET / HTTP/1.0\r\n\r\n' |>>>
>>> Ether()/IP()/IP()/UDP()
<Ether type=0x800 |<IP frag=0 proto=IP |<IP frag=0 proto=UDP |<UDP |>>>>
>>> IP(proto=55)/TCP()
<IP frag=0 proto=55 |<TCP |>>

Voici l'explication:

L'opérateur / a été utilisé comme opérateur de composition entre deux couches. Ce faisant, la couche inférieure peut avoir un ou plusieurs de ses champs par défaut surchargés en fonction de la couche supérieure. (Vous pouvez toujours donner la valeur que vous voulez). Une chaîne peut être utilisée comme couche brute.

Sarien
la source
0

Alternatives à la prise en charge native de la surcharge des opérateurs Java

Comme Java n'a pas de surcharge d'opérateur, voici quelques alternatives que vous pouvez étudier:

  1. Utilisez une autre langue. Les deux Groovy et Scala ont la surcharge des opérateurs, et sont basés sur Java.
  2. Utilisez java-oo , un plugin qui permet la surcharge des opérateurs en Java. Notez qu'il n'est PAS indépendant de la plateforme. En outre, il présente de nombreux problèmes et n'est pas compatible avec les dernières versions de Java (c'est-à-dire Java 10). ( Source d'origine StackOverflow )
  3. Utilisez JNI , Java Native Interface ou des alternatives. Cela vous permet d'écrire des méthodes C ou C ++ (peut-être d'autres?) À utiliser en Java. Bien sûr, cela n'est PAS indépendant de la plateforme.

Si quelqu'un est au courant des autres, veuillez commenter et je l'ajouterai à cette liste.

gagarwa
la source
0

Bien que le langage Java ne prenne pas directement en charge la surcharge des opérateurs, vous pouvez utiliser le plug-in de compilation Manifold dans n'importe quel projet Java pour l'activer. Il prend en charge Java 8-13 (la version Java actuelle) et est entièrement pris en charge dans IntelliJ IDEA.

Scott
la source