489 Stimmen

Warum bietet Java keine Operatorüberladung?

Wenn man von C++ zu Java wechselt, ist die offensichtliche unbeantwortete Frage, warum Java keine Operatorüberladung enthält.

Ist nicht Complex a, b, c; a = b + c; viel einfacher als Complex a, b, c; a = b.add(c); ?

Gibt es dafür einen bekannten Grund, stichhaltige Argumente für no das Überladen von Operatoren zuzulassen? Ist der Grund willkürlich oder aus Zeitmangel?

6 Stimmen

1 Stimmen

@zzzz, es fällt mir schwer, diesen Artikel zu lesen. Wurde er automatisch übersetzt, oder ist Englisch die zweite Sprache des Autors? Ich finde die Diskussion hier viel sauberer.

48 Stimmen

An die Leute, die dies als nicht konstruktiv abtun: Diese Frage hat einen der konstruktivsten Dialoge hervorgebracht, die ich bei SO gesehen habe. Vielleicht ist es ein besserer Kandidat für programmers.stackexchange.com aber es gibt Zeiten, in denen ich denke, dass SO übermäßig abweisend gegenüber breiteren Themen ist.

918voto

paercebal Punkte 78198

In vielen Beiträgen wird über die Überlastung der Bediener geklagt.

Ich hatte das Gefühl, dass ich die Konzepte des "Operator Overloading" klären musste, indem ich einen alternativen Blickwinkel auf dieses Konzept anbiete.

Code-Verschleierung?

Dieses Argument ist ein Trugschluss.

Obfuscating ist in allen Sprachen möglich...

Es ist genauso einfach, Code in C oder Java durch Funktionen/Methoden zu verschleiern wie in C++ durch Operatorüberladungen:

// 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 ;
}

...Selbst in den Standardschnittstellen von Java

Ein weiteres Beispiel ist die Cloneable Schnittstelle in Java:

Sie sollen das Objekt, das diese Schnittstelle implementiert, klonen. Aber Sie könnten lügen. Und ein anderes Objekt erstellen. Tatsächlich ist diese Schnittstelle so schwach, dass Sie einen anderen Objekttyp zurückgeben könnten, nur so zum Spaß:

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

Da die Cloneable Schnittstelle missbraucht/verschleiert werden kann, sollte sie aus denselben Gründen verboten werden, aus denen die Überladung von C++-Operatoren verboten werden soll?

Wir könnten die Überlastung der toString() Methode eines MyComplexNumber Klasse, um die Stunde des Tages in Form einer Zeichenkette zurückzugeben. Sollte die toString() Überlastung auch verboten werden? Wir könnten sabotieren MyComplexNumber.equals einen Zufallswert zurückgeben, die Operanden ändern usw. usw. usw..

In Java, wie auch in C++ oder einer anderen Sprache, muss der Programmierer beim Schreiben von Code ein Minimum an Semantik beachten. Das bedeutet die Implementierung einer add Funktion, die addiert, und Cloneable Implementierungsmethode, die klont, und eine ++ Operator als Inkremente.

Was ist überhaupt eine Verschleierung?

Da wir nun wissen, dass Code sogar durch die makellosen Java-Methoden sabotiert werden kann, können wir uns die Frage nach dem wirklichen Nutzen der Operatorüberladung in C++ stellen?

Klare und natürliche Notation: Methoden vs. Operatorüberladung?

Im Folgenden werden wir für verschiedene Fälle den "gleichen" Code in Java und C++ vergleichen, um eine Vorstellung davon zu bekommen, welcher Kodierungsstil klarer ist.

Natürliche Vergleiche:

// 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 ;

Bitte beachten Sie, dass A und B in C++ von beliebigem Typ sein können, solange die Operatorüberladungen vorhanden sind. Wenn A und B in Java keine Primitive sind, kann der Code sehr verwirrend werden, selbst für primitiv-ähnliche Objekte (BigInteger usw.)...

Natürliche Array/Container-Zugriffsmöglichkeiten und Subscripting:

// 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

In Java gibt es für jeden Container, der das Gleiche tun soll (Zugriff auf seinen Inhalt über einen Index oder Bezeichner), eine andere Methode, was verwirrend ist.

In C++ verwendet jeder Container dank der Überladung durch Operatoren denselben Weg, um auf seinen Inhalt zuzugreifen.

Natürliche fortgeschrittene Typen Manipulation

Die folgenden Beispiele verwenden eine Matrix Objekt, das über die ersten Links gefunden wurde, die bei Google für " Java-Matrix-Objekt " und " C++ Matrix-Objekt ":

// 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

Und dies ist nicht auf Matrizen beschränkt. Die BigInteger y BigDecimal Klassen in Java leiden unter der gleichen verwirrenden Ausführlichkeit, während ihre Entsprechungen in C++ so klar sind wie eingebaute Typen.

Natürliche Iteratoren:

// 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

Natürliche Funktoren:

// C++ Functors
myFunctorObject("Hello World", 42) ;

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

Textverkettung:

// 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, in Java können Sie verwenden MyString = "Hello " + 25 + " World" ; auch... Aber, Moment mal: Das ist doch eine Überlastung des Operators, oder? Ist das nicht Betrug???

-D

Generischer Code?

Derselbe generische Code, der Operanden ändert, sollte sowohl für Built-Ins/Primitives (die in Java keine Schnittstellen haben), Standardobjekte (die möglicherweise nicht die richtige Schnittstelle haben) als auch für benutzerdefinierte Objekte verwendet werden können.

Zum Beispiel die Berechnung des Durchschnittswerts von zwei Werten beliebigen Typs:

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

Diskussion über das Überladen von Operatoren

Nachdem wir nun einen fairen Vergleich zwischen C++-Code, der Operatorüberladung verwendet, und demselben Code in Java gesehen haben, können wir nun das Konzept der "Operatorüberladung" diskutieren.

Die Überlastung von Operatoren gab es schon vor dem Computer

*Auch außerhalb der Informatik gibt es die Überlastung von Operatoren: In der Mathematik gibt es zum Beispiel Operatoren wie + , - , `` usw. sind überlastet.**

In der Tat, die Bedeutung von + , - , * usw. ändert sich je nach Art der Operanden (Zahlen, Vektoren, Quantenwellenfunktionen, Matrizen usw.).

Die meisten von uns haben im Rahmen ihrer naturwissenschaftlichen Ausbildung mehrere Bedeutungen für Operatoren gelernt, die von den Typen der Operanden abhängen. Fanden wir sie also verwirrend?

Die Überladung von Operatoren hängt von ihren Operanden ab

Dies ist der wichtigste Teil der Operator-Überladung: Wie in der Mathematik oder in der Physik hängt die Operation von den Typen ihrer Operanden ab.

Wenn Sie also den Typ des Operanden kennen, wissen Sie auch, welche Auswirkungen die Operation hat.

Sogar C und Java haben (fest kodierte) Operatorüberladung

In C ändert sich das tatsächliche Verhalten eines Operators je nach seinen Operanden. Die Addition zweier ganzer Zahlen unterscheidet sich beispielsweise von der Addition zweier Paschas oder sogar einer ganzen Zahl und eines Paschas. Es gibt sogar den ganzen Bereich der Zeigerarithmetik (ohne Casting kann man zu einem Zeiger eine ganze Zahl addieren, aber man kann nicht zwei Zeiger addieren...).

In Java gibt es keine Zeigerarithmetik, aber jemand hat die Stringverkettung ohne die + Operator wäre lächerlich genug, um eine Ausnahme im Credo "Operatorüberladung ist böse" zu rechtfertigen.

Es ist nur so, dass Sie als C- (aus historischen Gründen) oder Java- (aus persönliche Gründe (siehe unten), können Sie keinen eigenen Kodierer bereitstellen.

In C++ ist das Überladen von Operatoren nicht optional...

In C++ ist das Überladen von Operatoren für eingebaute Typen nicht möglich (und das ist auch gut so), aber benutzerdefiniert Typen können haben benutzerdefiniert Operator-Überlastungen.

Wie bereits erwähnt, werden in C++, im Gegensatz zu Java, Benutzertypen nicht als Bürger zweiter Klasse der Sprache betrachtet, wenn sie mit eingebauten Typen verglichen werden. Wenn also eingebaute Typen über Operatoren verfügen, sollten auch Benutzertypen in der Lage sein, sie zu verwenden.

Die Wahrheit ist, dass, wie die toString() , clone() , equals() Methoden sind für Java ( d.h. quasi-standardmäßig ), ist die Überladung von C++-Operatoren so sehr Teil von C++, dass sie so selbstverständlich wird wie die ursprünglichen C-Operatoren oder die zuvor erwähnten Java-Methoden.

In Verbindung mit der Schablonenprogrammierung wird das Überladen von Operatoren zu einem bekannten Entwurfsmuster. Tatsächlich kommt man in der STL nicht sehr weit, ohne überladene Operatoren und überladende Operatoren für die eigene Klasse zu verwenden.

...aber es sollte nicht missbraucht werden

Bei der Überladung von Operatoren sollte die Semantik des Operators beachtet werden. Subtrahieren Sie nicht in einer + Operator (wie in "nicht subtrahieren in einer add Funktion" oder "Rückgabe-Mist in einer clone Methode").

Cast-Überladungen können sehr gefährlich sein, da sie zu Mehrdeutigkeiten führen können. Sie sollten daher wirklich nur für genau definierte Fälle verwendet werden. Was die && y || Überlasten Sie sie niemals, es sei denn, Sie wissen wirklich, was Sie tun, da Sie sonst die Kurzschlussauswertung verlieren, die die einheimischen Betreiber && y || genießen.

Also... Ok... Warum ist es dann in Java nicht möglich?

Weil James Gosling es gesagt hat:

Ich habe das Überladen von Operatoren als eine ziemlich persönliche Entscheidung weil ich gesehen hatte, dass zu viele Leute sie in C++ missbrauchen.

_James Gosling. Quelle: http://www.gotw.ca/publications/c_family_interview.htm_

Bitte vergleichen Sie den Text von Gosling oben mit dem von Stroustrup unten:

Viele C++-Entwurfsentscheidungen haben ihre Wurzeln in meiner Abneigung, Menschen zu zwingen, Dinge auf eine bestimmte Weise zu tun [...] Oft war ich versucht, eine Funktion zu verbieten, die ich persönlich nicht mochte, aber ich habe davon abgesehen, weil Ich habe nicht geglaubt, dass ich das Recht habe, anderen meine Ansichten aufzuzwingen. .

Bjarne Stroustrup. Quelle: Der Entwurf und die Entwicklung von C++ (1.3 Allgemeiner Hintergrund)

Würde die Überladung von Operatoren Java nützen?

Einige Objekte würden von der Überladung mit Operatoren sehr profitieren (konkrete oder numerische Typen, wie BigDecimal, komplexe Zahlen, Matrizen, Container, Iteratoren, Komparatoren, Parser usw.).

In C++ können Sie von diesem Vorteil profitieren, weil Stroustrup so bescheiden ist. In Java ist man einfach aufgeschmissen, weil Goslings persönliche Wahl .

Könnte es zu Java hinzugefügt werden?

Die Gründe dafür, dass das Operator-Overloading jetzt nicht in Java hinzugefügt wird, könnten eine Mischung aus interner Politik, Allergie gegen das Feature, Misstrauen gegenüber Entwicklern (Sie wissen schon, die Saboteure, die Java-Teams heimzusuchen scheinen...), Kompatibilität mit den früheren JVMs, Zeit, um eine korrekte Spezifikation zu schreiben, usw. sein.

Warten Sie also nicht zu lange auf diese Funktion...

Aber sie tun es in C#!!!

Ja...

Dies ist zwar bei weitem nicht der einzige Unterschied zwischen den beiden Sprachen, aber ich finde ihn immer wieder amüsant.

Offensichtlich haben die C#-Leute mit ihrem "Jedes Primitivum ist ein struct und eine struct leitet sich von Object" ab hat es beim ersten Versuch richtig gemacht.

Und sie tun es in andere Sprachen !!!

Trotz all des FUD gegen das Überladen von definierten Operatoren unterstützen die folgenden Sprachen dies: Kotlin , Scala , Dart , Python , F# , C# , D , Algol 68 , Smalltalk , Groovy , Raku (früher Perl 6) , C++, Ruby , Haskell , MATLAB , Eiffel , Lua , Clojure , Fortran 90 , Schnell , Ada , Delphi 2005 ...

So viele Sprachen, mit so vielen unterschiedlichen (und manchmal gegensätzlichen) Philosophien, und doch sind sie sich in diesem Punkt alle einig.

Ein Denkanstoß...

57 Stimmen

Das ist eine ausgezeichnete Antwort. Ich stimme ihr nicht zu, aber es ist trotzdem eine ausgezeichnete Antwort. Ich denke, die Probleme, die mit schlechten Überlastungen möglich sind, übersteigen den Wert der guten Überlastungen.

1 Stimmen

Ich schätze, du hast ein "append" bei dem Java-Beispiel für die String-Verkettung vergessen. Außerdem haben Sie ein oder zwei Leerzeichen innerhalb der Zeichenketten vergessen :)

0 Stimmen

@Denilson Sá: Ich habe den Code wie von Ihnen vorgeschlagen korrigiert. Danke für deinen Kommentar... ^_^ ...

44voto

Garth Gilmour Punkte 10840

James Gosling verglich die Entwicklung von Java mit folgendem:

"Es gibt dieses Prinzip beim Umzug, wenn man von einer Wohnung in eine andere zieht. Ein interessantes Experiment ist es, die Wohnung einzupacken und alles in Kartons zu packen, dann in die nächste Wohnung zu ziehen und nichts auszupacken, bis man es braucht. Wenn Sie also Ihre erste Mahlzeit zubereiten, nehmen Sie etwas aus einer Schachtel. Nach einem Monat oder so hast du dann herausgefunden, welche Dinge du in deinem Leben wirklich brauchst, und dann nimmst du den Rest der Sachen - und vergisst, wie sehr du sie magst oder wie cool sie sind - und wirfst sie einfach weg. Es ist erstaunlich, wie sehr das dein Leben vereinfacht, und man kann dieses Prinzip auf alle möglichen Designfragen anwenden: Dinge nicht nur tun, weil sie cool sind oder weil sie interessant sind."

Sie können die Kontext des Zitats hier

Grundsätzlich ist die Überladung von Operatoren für eine Klasse, die eine Art von Punkt, Währung oder komplexe Zahl modelliert, großartig. Aber danach gehen einem schnell die Beispiele aus.

Ein weiterer Faktor war der Missbrauch der Funktion in C++ durch Entwickler, die Operatoren wie '&&', '||', die Cast-Operatoren und natürlich 'new' überladen haben. Die Komplexität, die sich aus der Kombination mit Wertübergabe und Ausnahmen ergibt, wird in der Außergewöhnliches C++ Buch.

6 Stimmen

Könnten Sie ein Codebeispiel für "die Komplexität der Operatorüberladung in Kombination mit Pass-by-Value und Ausnahmen" geben? Trotz einiger Jahre, in denen ich mit der Sprache gespielt habe, und trotz des Besitzes und der Lektüre aller effektiven/ausgefallenen Bücher über C++ verstehe ich nicht, was Sie damit meinen.

71 Stimmen

Was bei James Gosling funktioniert, wird nicht bei jedem funktionieren. Er ist unglaublich kurzsichtig, wenn er sein "interessantes" Packexperiment auf "Wirf alles in der Welt weg, was ich nicht brauche, damit niemand das Zeug benutzen kann" hochrechnet. Er weiß offensichtlich nicht, was ich brauche oder benutze.

54 Stimmen

@B T : Am aufschlussreichsten ist Goslings Sichtweise im Vergleich zu Stroustrups Sichtweise zu diesem Thema: 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) .

24voto

user15793 Punkte 257

Sehen Sie sich Boost.Units an: Linktext

Es bietet eine dimensionslose Analyse ohne Aufwand durch Überladen von Operatoren. Wie viel klarer kann das noch werden?

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

würde tatsächlich "Energie = 4 J" ausgeben, was korrekt ist.

1 Stimmen

"Wie genau erschwert das die Wartung und wo in aller Welt wird der Code dadurch verschleiert?"

16voto

Sebastian Redl Punkte 64533

Die Java-Entwickler haben beschlossen, dass die Überladung von Operatoren mehr Ärger bedeutet, als sie wert ist. So einfach ist das.

In einer Sprache, in der jede Objektvariable eigentlich eine Referenz ist, hat die Überladung von Operatoren den zusätzlichen Nachteil, dass sie ziemlich unlogisch ist - zumindest für einen C++-Programmierer. Vergleichen Sie die Situation mit der ==-Operatorüberladung in C# und Object.Equals y Object.ReferenceEquals (oder wie auch immer es genannt wird).

12voto

Aaron Punkte 3397

Angenommen, Sie wollten den vorherigen Wert des Objekts überschreiben, auf das von a dann müsste eine Mitgliedsfunktion aufgerufen werden.

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

In C++ weist dieser Ausdruck den Compiler an, drei (3) Objekte auf dem Stapel zu erstellen, die Addition durchzuführen und kopieren. den sich ergebenden Wert aus dem temporären Objekt in das bestehende Objekt a .

In Java jedoch, operator= führt keine Wertkopie für Referenztypen durch, und Benutzer können nur neue Referenztypen, aber keine Werttypen erstellen. Also für einen benutzerdefinierten Typ namens Complex Zuweisung bedeutet, einen Verweis auf einen vorhandenen Wert zu kopieren.

Überlegen Sie stattdessen:

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

In C++ wird dabei der Wert kopiert, so dass der Vergleich nicht-gleich ergibt. In Java, operator= führt eine Referenzkopie durch, also a y b beziehen sich nun auf denselben Wert. Infolgedessen wird der Vergleich "gleich" ergeben, da das Objekt mit sich selbst verglichen wird.

Der Unterschied zwischen Kopien und Referenzen trägt nur zur Verwirrung der Operatorüberladung bei. Wie @Sebastian erwähnte, müssen sowohl Java als auch C# die Gleichheit von Werten und Referenzen getrennt behandeln -- operator+ würde sich wahrscheinlich mit Werten und Objekten befassen, aber operator= ist bereits implementiert, um mit Referenzen umzugehen.

In C++ sollte man sich jeweils nur mit einer Art von Vergleich befassen, damit es weniger verwirrend ist. Zum Beispiel bei Complex , operator= y operator== arbeiten beide mit Werten - sie kopieren Werte und vergleichen Werte.

1 Stimmen

Zu Ihrer Information: Ich habe diese Frage nicht gestellt, ich habe nur die Tags bearbeitet. Klicken Sie auf die Uhrzeit (derzeit steht dort 'vor einer Stunde') über meinem Namen, um weitere Informationen zu erhalten - es sieht aus wie Rengolin ( stackoverflow.com/benutzer/14067/rengolin ) die Frage gestellt.

7 Stimmen

Es ist eigentlich ganz einfach... Tun Sie einfach wie Python und haben keine überladenen Zuordnung.

257 Stimmen

Diese Antwort beantwortet die Frage überhaupt nicht. Sie stören sich einfach an der Verwendung des Gleichheitszeichens in Java. Wenn b+C einen neuen Komplex zurückgeben würde, dann wäre a = b+c vollkommen gültig und auch viel einfacher zu lesen. Selbst wenn Sie a an Ort und Stelle modifizieren wollten, ist a.set(b+c) viel einfacher zu lesen - besonders wenn die Arithmetik mehr als trivial ist: a.set((a b + b c)/5) oder a = a.multiply(b).add(b.multiply(c)).divide(5). Ihre Wahl

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X