¿Por qué Java no ofrece sobrecarga al operador?

Viniendo de C ++ a Java, la pregunta obvia que queda por responder es por qué Java no incluyó la sobrecarga del operador.

No es Complex a, b, c; a = b + c; Complex a, b, c; a = b + c; mucho más simple que el Complex a, b, c; a = b.add(c); Complex a, b, c; a = b.add(c); ?

¿Hay alguna razón conocida para esto, argumentos válidos para no permitir la sobrecarga del operador? ¿La razón es arbitraria o se pierde en el tiempo?

Suponiendo que desea sobrescribir el valor anterior del objeto al que hace referencia a, se debería invocar una función miembro.

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

En C ++, esta expresión le dice al comstackdor que cree tres (3) objetos en la stack, realice una adición y copie el valor resultante del objeto temporal en el objeto existente a .

Sin embargo, en Java, operator= no realiza una copia de valor para los tipos de referencia, y los usuarios solo pueden crear nuevos tipos de referencia, no tipos de valores. Entonces, para un tipo definido por el usuario llamado Complex , la asignación significa copiar una referencia a un valor existente.

Considere en su lugar:

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

En C ++, esto copia el valor, por lo que la comparación resultará no igual. En Java, operator= realiza una copia de referencia, por lo que a y b ahora se refieren al mismo valor. Como resultado, la comparación producirá ‘igual’, ya que el objeto se comparará igual a sí mismo.

La diferencia entre las copias y las referencias solo se sum a la confusión de la sobrecarga del operador. Como mencionó @Sebastian, Java y C # tienen que tratar el valor y la igualdad de referencia por separado: es probable que el operator+ trate con valores y objetos, pero el operator= ya está implementado para manejar las referencias.

En C ++, solo debería tratar con un tipo de comparación a la vez, por lo que puede ser menos confuso. Por ejemplo, en Complex , operator= y operator== trabajan en valores, copiando valores y comparando valores respectivamente.

Hay muchos mensajes que se quejan sobre la sobrecarga del operador.

Sentí que tenía que aclarar los conceptos de “sobrecarga del operador”, ofreciendo un punto de vista alternativo sobre este concepto.

¿El código ofusca?

Este argumento es una falacia

Ofuscación es posible en todos los idiomas …

Es tan fácil ofuscar el código en C o Java a través de funciones / métodos como en C ++ a través de las sobrecargas del operador:

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

… Incluso en las interfaces estándar de Java

Para otro ejemplo, veamos la interfaz Cloneable en Java:

Se supone que debes clonar el objeto que implementa esta interfaz. Pero podrías mentir Y crea un objeto diferente. De hecho, esta interfaz es tan débil que podría devolver otro tipo de objeto por completo, solo por diversión:

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

Como la interfaz Cloneable puede ser abusada / ofuscada, ¿debería ser prohibida por los mismos motivos que se supone que es la sobrecarga del operador C ++?

Podríamos sobrecargar el método toString() de una clase MyComplexNumber para que devuelva la hora del día codificada. ¿Debería prohibirse también la sobrecarga de toString() ? Podríamos sabotear MyComplexNumber.equals para que devuelva un valor aleatorio, modificar los operandos … etc. etc. etc.

En Java, como en C ++, o en cualquier idioma, el progtwigdor debe respetar un mínimo de semántica al escribir el código. Esto significa implementar una función de add que agrega, y Cloneable método de implementación Cloneable que clona, ​​y un operador ++ que se incrementa.

¿Qué está ofuscando de todos modos?

Ahora que sabemos que el código puede ser saboteado incluso a través de los prístinos métodos de Java, podemos preguntarnos sobre el uso real de la sobrecarga del operador en C ++.

Notación clara y natural: métodos contra sobrecarga del operador?

Compararemos a continuación, para diferentes casos, el “mismo” código en Java y C ++, para tener una idea de qué tipo de estilo de encoding es más claro.

Comparaciones naturales:

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

Tenga en cuenta que A y B pueden ser de cualquier tipo en C ++, siempre que se proporcionen las sobrecargas del operador. En Java, cuando A y B no son primitivos, el código puede llegar a ser muy confuso, incluso para objetos primitivos (BigInteger, etc.) ...

Atributos y anexos naturales de matriz / contenedor:

 // 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, vemos que para que cada contenedor haga lo mismo (acceder a su contenido a través de un índice o identificador), tenemos una manera diferente de hacerlo, lo cual es confuso.

En C ++, cada contenedor utiliza la misma forma de acceder a su contenido, gracias a la sobrecarga del operador.

Manipulación de tipos naturales avanzados

Los siguientes ejemplos usan un objeto Matrix , que se encuentra utilizando los primeros enlaces encontrados en Google para " objeto Java Matrix " y " objeto 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 

Y esto no está limitado a las matrices. Las clases BigInteger y BigDecimal de Java adolecen de la misma verbosidad confusa, mientras que sus equivalentes en C ++ son tan claros como los tipos incorporados.

Iteradores naturales:

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

Funtores naturales:

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

Concatenación de texto:

 // 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 puedes usar MyString = "Hello " + 25 + " World" ; también ... Pero, espere un segundo: esta es la sobrecarga del operador, ¿no? ¿No está engañando?

:-RE

Código genérico?

Los mismos operandos generics de modificación de código deberían poder utilizarse tanto para incorporaciones / primitivas (que no tienen interfaces en Java), objetos estándar (que no podrían tener la interfaz correcta) y objetos definidos por el usuario.

Por ejemplo, calculando el valor promedio de dos valores de tipos arbitrarios:

 // 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 Matrix matrixValue = getAverage(mA, mB) ; // mA, mB are Matrix // Java primitive/advanced types // It won't really work in Java, even with generics. Sorry. 

Discutiendo la sobrecarga del operador

Ahora que hemos visto comparaciones justas entre el código de C ++ mediante la sobrecarga del operador y el mismo código en Java, ahora podemos hablar de "sobrecarga del operador" como concepto.

La sobrecarga del operador existía desde antes de las computadoras

Incluso fuera de la informática, existe una sobrecarga del operador: por ejemplo, en matemáticas, los operadores como + , - , * , etc. están sobrecargados.

De hecho, la significación de + , - , * , etc. cambia dependiendo de los tipos de operandos (numéricos, vectores, funciones de onda cuántica, matrices, etc.).

La mayoría de nosotros, como parte de nuestros cursos de ciencias, aprendimos múltiples significados para los operadores, dependiendo de los tipos de operandos. ¿Los encontramos confusos, ellos?

La sobrecarga del operador depende de sus operandos

Esta es la parte más importante de la sobrecarga del operador: al igual que en matemáticas, o en física, la operación depende de los tipos de sus operandos.

Entonces, conozca el tipo de operando y sabrá el efecto de la operación.

Incluso C y Java tienen una sobrecarga del operador (codificada)

En C, el comportamiento real de un operador cambiará de acuerdo con sus operandos. Por ejemplo, agregar dos enteros es diferente a agregar dos dobles, o incluso un entero y un doble. Incluso hay todo el dominio aritmético del puntero (sin conversión, puede agregar un puntero a un entero, pero no puede agregar dos punteros ...).

En Java, no hay aritmética de puntero, pero alguien aún encuentra que la concatenación de cadenas sin el operador + sería lo suficientemente ridícula como para justificar una excepción en el credo de "la sobrecarga del operador es malo".

Es solo que usted, como codificador C (por razones históricas) o Java (por razones personales , vea a continuación), no puede proporcionar el suyo propio.

En C ++, la sobrecarga del operador no es opcional ...

En C ++, la sobrecarga del operador para los tipos incorporados no es posible (y esto es algo bueno), pero los tipos definidos por el usuario pueden tener sobrecargas de operador definidas por el usuario .

Como ya se dijo anteriormente, en C ++, y por el contrario a Java, los usuarios no se consideran ciudadanos de segunda clase del idioma, en comparación con los tipos incorporados. Entonces, si los tipos incorporados tienen operadores, los tipos de usuario también deberían poder tenerlos.

La verdad es que, al igual que los métodos toString() , clone() , equals() son para Java ( es decir, similar a cuasi-estándar ), la sobrecarga del operador C ++ es tan parte de C ++ que se vuelve tan natural como los operadores C originales , o los métodos Java antes mencionados.

Combinado con la progtwigción de plantillas, la sobrecarga del operador se convierte en un patrón de diseño bien conocido. De hecho, no puede llegar muy lejos en STL sin utilizar operadores sobrecargados y sobrecargar operadores para su propia clase.

... pero no se debe abusar

La sobrecarga del operador debe esforzarse por respetar la semántica del operador. No restar en un operador + (como en "no restar en una función de add ", o "devolver basura en un método de clone ").

La sobrecarga del cast puede ser muy peligrosa porque puede generar ambigüedades. Por lo tanto, deberían reservarse para casos bien definidos. En cuanto a && y || , no los sobrecargues nunca a menos que realmente sepas lo que estás haciendo, ya que perderás la evaluación de cortocircuito que los operadores nativos && y || disfrutar.

Entonces ... Ok ... ¿Entonces por qué no es posible en Java?

Porque James Gosling lo dijo así:

Dejé la sobrecarga del operador como una elección bastante personal porque había visto demasiadas personas abusar de ella en C ++.

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

Por favor compare el texto de Gosling arriba con Stroustrup a continuación:

Muchas decisiones de diseño C ++ tienen sus raíces en mi disgusto por obligar a las personas a hacer las cosas de una manera particular [...] A menudo, estuve tentado de prohibir una característica que personalmente no me gustaba, me abstuve de hacerlo porque no pensaba que tenía el derecho de forzar mis puntos de vista sobre otros .

Bjarne Stroustrup. Fuente: El diseño y la evolución de C ++ (1.3 Antecedentes generales)

¿La sobrecarga del operador beneficiaría a Java?

Algunos objetos se beneficiarían enormemente de la sobrecarga del operador (tipos concretos o numéricos, como BigDecimal, números complejos, matrices, contenedores, iteradores, comparadores, analizadores, etc.).

En C ++, puede beneficiarse de este beneficio gracias a la humildad de Stroustrup. En Java, simplemente estás jodido por la elección personal de Gosling.

¿Podría ser agregado a Java?

Las razones para no agregar sobrecarga de operador ahora en Java podrían ser una mezcla de política interna, alergia a la característica, desconfianza de los desarrolladores (ya sabes, los saboteadores que parecen perseguir a los equipos de Java ...), compatibilidad con las JVM anteriores, tiempo para escribir una especificación correcta, etc.

Así que no aguante la respiración esperando esta característica ...

¡Pero lo hacen en C #!

Sí...

Si bien esto está lejos de ser la única diferencia entre los dos idiomas, este nunca deja de divertirme.

Aparentemente, la gente de C #, con su "cada primitiva es una struct y una struct deriva de Objeto" , lo consiguió al primer bash.

¡Y lo hacen en otros idiomas !

A pesar de todo el FUD contra la sobrecarga de operador definida, los siguientes idiomas lo admiten: 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 ...

Tantos idiomas, con tantas filosofías diferentes (ya veces opuestas), y sin embargo, todos están de acuerdo en ese punto.

Comida para el pensamiento...

James Gosling comparó el diseño de Java con lo siguiente:

“Hay un principio para mudarse, cuando pasas de un apartamento a otro. Un experimento interesante es empacar tu apartamento y poner todo en cajas, luego mudarse al siguiente apartamento y no desempaquetar nada hasta que lo necesites. Estás haciendo tu primera comida, y estás sacando algo de una caja. Luego, después de un mes o así, lo has usado para averiguar qué cosas de tu vida realmente necesitas, y luego tomas el rest del cosas – olvide lo mucho que le gusta o lo genial que es – y simplemente tírelo. Es increíble cómo eso simplifica su vida, y puede usar ese principio en todo tipo de problemas de diseño: no hacer las cosas solo porque es genial o simplemente porque son interesantes “.

Puedes leer el contexto de la cita aquí

Básicamente, la sobrecarga del operador es excelente para una clase que modela algún tipo de punto, moneda o número complejo. Pero después de eso empiezas a quedarte sin ejemplos rápidamente.

Otro factor fue el abuso de la característica en C ++ por los desarrolladores que sobrecargaron operadores como ‘&&’, ‘||’, los operadores de elenco y por supuesto ‘nuevo’. La complejidad resultante de combinar esto con pasar por valor y excepciones está bien cubierta en el libro Exceptional C ++ .

Consulte Boost.Units: enlace de texto

Proporciona un análisis dimensional cero-sobrecarga a través de la sobrecarga del operador. ¿Cuánto más claro puede ser esto?

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

en realidad produciría "Energía = 4 J", que es correcto.

Los diseñadores de Java decidieron que la sobrecarga del operador era más problemática de lo que valía. Simple como eso.

En un lenguaje donde cada variable de objeto es realmente una referencia, la sobrecarga del operador tiene el riesgo adicional de ser bastante ilógico, al menos para un progtwigdor de C ++. Compare la situación con la sobrecarga del operador C = == y Object.Equals y Object.ReferenceEquals (o como se llame).

Groovy tiene sobrecarga de operador y se ejecuta en la JVM. Si no te importa el golpe de rendimiento (que se reduce cada día). Es automático basado en nombres de métodos. por ejemplo, ‘+’ llama al método ‘más (argumento)’.

Creo que esto puede haber sido una opción de diseño consciente para obligar a los desarrolladores a crear funciones cuyos nombres comuniquen claramente sus intenciones. En C ++, los desarrolladores sobrecargarían a los operadores con funcionalidades que a menudo no tendrían relación con la naturaleza comúnmente aceptada del operador dado, por lo que es casi imposible determinar qué hace un fragmento de código sin mirar la definición del operador.

Bueno, realmente puedes dispararte en el pie con la sobrecarga del operador. Es como con los indicadores, las personas cometen errores estúpidos con ellos, por lo que se decidió quitar las tijeras.

Al menos creo que esa es la razón. Estoy de tu lado de todos modos. 🙂

Decir que la sobrecarga del operador lleva a errores lógicos de tipo que el operador no concuerda con la lógica de operación, es como no decir nada. El mismo tipo de error ocurrirá si el nombre de la función no es apropiado para la lógica de operación, entonces, ¿cuál es la solución: descarte la capacidad de uso de la función? Esta es una respuesta cómica: “Inapropiado para la lógica de operación”, cada nombre de parámetro, cada clase, función o lo que sea puede ser lógicamente inapropiado. Creo que esta opción debería estar disponible en un lenguaje de progtwigción respetable, y aquellos que piensan que es inseguro, bueno, ninguno dice que tienes que usarlo. Vamos a tomar el C #. Dejaron caer los indicadores, pero hey, hay un progtwig de statement de “código inseguro” que te gusta bajo tu propio riesgo.

Técnicamente, existe una sobrecarga del operador en cada lenguaje de progtwigción que puede tratar con diferentes tipos de números, por ejemplo, números enteros y reales. Explicación: El término sobrecarga significa que hay simplemente varias implementaciones para una función. En la mayoría de los lenguajes de progtwigción, se proporcionan diferentes implementaciones para el operador +, uno para enteros, uno para reales, esto se llama sobrecarga del operador.

Ahora, a muchas personas les resulta extraño que Java tenga una sobrecarga de operadores para el operador + para agregar cadenas, y desde un punto de vista matemático esto sería extraño, pero visto desde el punto de vista del desarrollador del lenguaje de progtwigción, no hay nada de malo en agregar una sobrecarga para el operador + para otras clases, por ejemplo, String. Sin embargo, la mayoría de las personas está de acuerdo en que una vez que agregue la sobrecarga integrada para + para String, entonces generalmente es una buena idea proporcionar esta funcionalidad para el desarrollador.

A estar completamente en desacuerdo con la falacia de que la sobrecarga del operador ofusca el código, ya que esto queda para que el desarrollador decida. Esto es ingenuo para pensar, y para ser honesto, se está haciendo viejo.

+1 para agregar sobrecarga del operador en Java 8.

Algunas personas dicen que la sobrecarga del operador en Java llevaría a la obstrucción. ¿Alguna vez esas personas se han detenido a mirar algún código Java haciendo algunas matemáticas básicas como boost un valor financiero en un porcentaje usando BigDecimal? … la verbosidad de tal ejercicio se convierte en su propia demostración de obstrucción. Irónicamente, agregar la sobrecarga del operador a Java nos permitiría crear nuestra propia clase Currency que haría que ese código matemático sea elegante y simple (menos obscurecido).

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() > 

Aquí está la explicación:

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