451 votes

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

En passant du C++ à Java, la question évidente qui reste 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; beaucoup plus simple que Complex a, b, c; a=b.add(c); ?

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

867voto

paercebal Points 38526

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

J'ai senti que je devais clarifier les concepts de "surcharge d'opérateurs", en offrant un point de vue alternatif sur ce concept.

Obfuscation du code ?

Cet argument est un sophisme.

L'obscurcissement est possible dans tous les langages...

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

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

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

Comme le Cloneable peut faire l'objet d'abus/obfuscation, devrait-elle être interdite au même titre que les surcharges d'opérateurs C++ ?

Nous pourrions surcharger le toString() méthode d'un MyComplexNumber pour qu'elle renvoie l'heure du jour sous forme de chaîne. Si la classe toString() la surcharge doit-elle aussi être interdite ? Nous pourrions saboter MyComplexNumber.equals pour qu'il renvoie une valeur aléatoire, modifie les opérandes... etc. etc. etc.

En Java, comme en C++, ou tout autre langage, le programmeur doit respecter un minimum de sémantique lorsqu'il écrit du code. Cela signifie qu'il faut implémenter un add qui ajoute, et Cloneable méthode de mise en œuvre qui clone, et une ++ que les incréments.

Qu'est-ce que l'obscurcissement de toute façon ?

Maintenant que nous savons que le code peut être saboté même à travers les méthodes vierges de Java, nous pouvons nous interroger sur la véritable utilité de la surcharge des opérateurs en C++ ?

Notation claire et naturelle : méthodes ou surcharge d'opérateurs ?

Nous allons comparer ci-dessous, pour différents cas, le "même" code en Java et en C++, pour avoir une idée du 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 n'importe quel type en C++, tant que les surcharges d'opérateurs sont fournies. En Java, lorsque A et B ne sont pas des primitives, le code peut devenir très confus, même pour les objets de type primitif (BigInteger, etc.)...

Des accesseurs et des indices naturels pour les tableaux et les conteneurs :

// 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.set("25", value) ;             // method set

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

En C++, chaque conteneur utilise la même manière d'accéder à son contenu, grâce à la surcharge des opérateurs.

Manipulation naturelle de types avancés

Les exemples ci-dessous utilisent un Matrix trouvé en utilisant les premiers liens trouvés sur Google pour " Objet Java Matrix " 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 site BigInteger et BigDecimal Les classes de Java souffrent de la même verbosité déroutante, 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<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

Fecteurs 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 de la surcharge d'opérateur, n'est-ce pas ? Ce n'est pas de la triche ???

-D

Un code générique ?

Le même code générique modifiant les opérandes devrait être utilisable à la fois pour les objets intégrés/primitifs (qui n'ont pas d'interface en Java), les objets standard (qui ne pourraient pas avoir la bonne interface) et les objets définis par l'utilisateur.

Par exemple, le calcul de 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.

Discussion sur la surcharge des opérateurs

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

La surcharge des opérateurs existait déjà avant les ordinateurs

*Même en dehors de l'informatique, il existe des surcharges d'opérateurs : Par exemple, en mathématiques, des opérateurs comme + , - , `` etc. sont surchargés.**

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

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

La surcharge des opérateurs dépend de leurs opérandes.

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

Ainsi, 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érateurs (codée en dur).

En C, le comportement réel d'un opérateur change en fonction de ses opérandes. Par exemple, l'addition de deux entiers est différente de l'addition de deux doubles, ou même d'un entier et d'un double. Il y a même tout le domaine de l'arithmétique des pointeurs (sans casting, on peut ajouter à un pointeur un entier, mais on ne peut 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 de caractères sans l'élément + serait suffisamment ridicule pour justifier une exception dans le credo "la surcharge des opérateurs est un mal".

C'est juste que vous, en tant qu'utilisateur de C (pour des raisons historiques) ou de Java (pour des raisons de sécurité), n'avez pas l'intention d'être un expert en la matière. raisons personnelles (voir ci-dessous), vous ne pouvez pas fournir votre propre codeur.

En C++, la surcharge des opérateurs n'est pas optionnelle...

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

Comme nous l'avons déjà dit, en C++, et contrairement à Java, les types 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 utilisateurs devraient pouvoir en avoir aussi.

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

Associée à la programmation par modèles, la surcharge des opérateurs devient un modèle de conception bien connu. En fait, vous ne pouvez pas aller très loin dans la STL sans utiliser des opérateurs surchargés, et sans surcharger des 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 effectuer de soustraction dans un + (comme dans "ne pas faire de soustraction dans une add fonction", ou "retour de merde dans une clone méthode").

La surcharge des cast peut être très dangereuse car elle peut conduire à des ambiguïtés. Elles doivent donc être réservées à des cas bien définis. Quant aux && et || Ne les surchargez jamais, à moins que vous ne sachiez vraiment ce que vous faites, car vous perdriez l'évaluation de court-circuit que les opérateurs natifs utilisent. && et || profiter.

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 d'opérateurs en tant que 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 celui de Stroustrup ci-dessous :

De nombreuses décisions relatives à la conception du C++ trouvent leur origine dans ma répugnance à forcer les gens à faire les choses d'une manière particulière [...] Souvent, j'ai été tenté de proscrire une fonctionnalité que je n'aimais pas personnellement, mais je me suis abstenu de le faire pour les raisons suivantes Je ne pensais pas avoir le droit d'imposer mon point de vue aux autres. .

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

La surcharge des opérateurs serait-elle bénéfique à 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 syntaxiques, etc.)

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

Pourrait-on l'ajouter à Java ?

Les raisons de ne pas ajouter la surcharge des opérateurs maintenant 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 JVM précédentes, de temps pour écrire une spécification correcte, etc.

Ne retenez donc 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 un struct dérive de l'objet" a réussi du premier coup.

44voto

Garth Gilmour Points 5219

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

"Il y a ce principe concernant les déménagements, lorsque vous passez d'un appartement à un autre. Une expérience intéressante consiste à emballer votre appartement et à tout mettre dans des cartons, puis à emménager dans l'appartement suivant et à ne rien déballer avant d'en avoir besoin. Ainsi, vous préparez votre premier repas, et vous sortez quelque chose d'une boîte. Puis, au bout d'un mois environ, vous avez utilisé cette méthode pour déterminer les choses dont vous avez réellement besoin dans votre vie, et vous prenez le reste des affaires - en oubliant à quel point vous les aimez ou à quel point elles sont cool - et vous les jetez. C'est incroyable comme cela simplifie votre vie, et vous pouvez utiliser ce principe dans toutes sortes de questions de conception : ne pas faire les choses juste parce qu'elles sont cool ou juste parce qu'elles sont intéressantes."

Vous pouvez lire le contexte de la citation ici

En fait, la surcharge des opérateurs est idéale pour une classe qui modélise un type de point, de devise ou de nombre complexe. Mais après cela, vous commencez à manquer d'exemples rapidement.

Un autre facteur a été l'utilisation abusive de la fonction en C++ par les développeurs qui ont surchargé des opérateurs tels que "&&", "||", les opérateurs cast et bien sûr "new". La complexité résultant de la combinaison de cette fonctionnalité avec le passage par la valeur et les exceptions est bien couverte dans le manuel de l'utilisateur. Un C++ exceptionnel livre.

23voto

user15793 Points 149

Vérifiez Boost.Units : texte du lien

Il fournit une analyse dimensionnelle sans frais généraux grâce à la surcharge des opérateurs. Comment pourrait-on ê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 "Énergie = 4 J", ce qui est correct.

14voto

Sebastian Redl Points 18816

Les concepteurs de Java ont décidé que la surcharge des opérateurs posait plus de problèmes qu'elle n'en valait la peine. C'est aussi simple que cela.

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

13voto

Aaron Points 2456

En supposant que le SCdF souhaite écraser la valeur précédente de l'objet auquel se réfère 'a', une fonction membre doit être invoquée.

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

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

Cependant, en java, l'opérateur= n'effectue pas de copie de valeur pour les types de référence, et les utilisateurs ne peuvent créer que de nouveaux types de référence, pas de types de valeur. Ainsi, pour un type défini par l'utilisateur appelé 'Complex', l'affectation signifie copier une référence à une valeur existante.

envisagez plutôt :

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

En C++, cette opération copie la valeur, de sorte que la comparaison donnera un résultat non égal. En Java, l'opérateur= effectue une copie de référence, de sorte que 'a' et 'b' font maintenant référence à la même valeur. Par conséquent, la comparaison produira "égal", puisque l'objet sera comparé à lui-même.

La différence entre les copies et les références ne fait qu'ajouter à la confusion de la surcharge des opérateurs. Comme Sebastian l'a mentionné, Java et C# doivent traiter séparément l'égalité des valeurs et des références -- l'opérateur+ traiterait probablement les valeurs et les objets, mais l'opérateur= 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, afin d'être moins confus. Par exemple, sur Complex, operator= et operator== travaillent tous deux sur des valeurs -- respectivement en copiant des valeurs et en comparant des valeurs.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X