Pregunta

Al pasar de C++ a Java, la pregunta obvia sin respuesta es ¿por qué Java no incluyó la sobrecarga de operadores?

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

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

¿Fue útil?

Solución

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

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

En C++, esta expresión le dice al compilador que cree tres (3) objetos en la pila, realice sumas y Copiar el valor resultante del objeto temporal al objeto existente a.

Sin embargo, en Java, operator= no realiza una copia de valores para los tipos de referencia y los usuarios solo pueden crear nuevos tipos de referencia, no tipos de valor.Entonces, para un tipo definido por el usuario llamado Complex, 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) ); // this assertion will fail

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 copias y referencias sólo aumenta la confusión de la sobrecarga del operador.Como mencionó @Sebastian, Java y C# tienen que lidiar con la igualdad de valores y referencias por separado. operator+ probablemente trataría con valores y objetos, pero operator= ya está implementado para tratar las referencias.

En C++, sólo deberías tratar con un tipo de comparación a la vez, para que pueda resultar menos confuso.Por ejemplo, en Complex, operator= y operator== Ambos están trabajando en valores: copiando valores y comparando valores respectivamente.

Otros consejos

Hay muchas publicaciones quejándose de la sobrecarga del operador.

Sentí que tenía que aclarar los conceptos de "sobrecarga de operadores", ofreciendo un punto de vista alternativo sobre este concepto.

¿Código ofuscado?

Este argumento es una falacia.

Ofuscar es posible en todos los idiomas...

Es tan fácil ofuscar código en C o Java a través de funciones/métodos como lo es en C++ a través de sobrecargas de operadores:

// 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 el Cloneable interfaz 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ías devolver otro tipo de objeto, sólo por diversión:

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

como el Cloneable Se puede abusar/ofuscar la interfaz, ¿debería prohibirse por los mismos motivos que se supone que lo es la sobrecarga del operador de C++?

Podríamos sobrecargar el toString() método de un MyComplexNumber class para que devuelva la hora del día en cadena.Si el toString() ¿Se prohibirá también la sobrecarga?Podríamos sabotear MyComplexNumber.equals para que devuelva un valor aleatorio, modifique los operandos...etc.etc.etc..

En Java, como en C++, o cualquier lenguaje, el programador debe respetar un mínimo de semántica a la hora de escribir código.Esto significa implementar un add función que suma, y Cloneable método de implementación que clona, ​​y un ++ operador que incrementos.

¿Qué es lo que confunde de todos modos?

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

Notación clara y natural:métodos vs.¿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 codificación 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 volverse muy confuso, incluso para objetos similares a primitivos (BigInteger, etc.)...

Accesores de matriz/contenedor natural y subíndices:

// 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 forma diferente de hacerlo, lo cual resulta confuso.

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

Manipulación de tipos avanzados naturales.

Los ejemplos siguientes utilizan un Matrix objeto, encontrado utilizando los primeros enlaces encontrados en Google para "Objeto de matriz Java" y "objeto matriz 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 se limita a las matrices.El BigInteger y BigDecimal Las clases de Java sufren de la misma verbosidad confusa, mientras que sus equivalentes en C++ son tan claros como los tipos integrados.

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

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 espera un segundo:Esto es una sobrecarga del operador, ¿no?¿No es trampa???

:-D

¿Código genérico?

Los mismos operandos genéricos de modificación de código deberían poder usarse tanto para elementos integrados/primitivos (que no tienen interfaces en Java), objetos estándar (que no pueden tener la interfaz correcta) como objetos definidos por el usuario.

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

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

Discutiendo la sobrecarga del operador

Ahora que hemos visto comparaciones justas entre el código C++ que utiliza la sobrecarga de operadores y el mismo código en Java, ahora podemos analizar la "sobrecarga de operadores" como concepto.

La sobrecarga de operadores existía desde antes de las computadoras

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

En efecto, el significado de +, -, *, etc.cambia dependiendo de los tipos de operandos (numéricos, vectores, funciones de onda cuánticas, 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?

La sobrecarga del operador depende de sus operandos.

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

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

Incluso C y Java tienen sobrecarga de operadores (codificados)

En C, el comportamiento real de un operador cambiará según sus operandos.Por ejemplo, sumar dos números enteros es diferente a sumar dos dobles, o incluso un entero y un doble.Incluso existe todo el dominio aritmético de punteros (sin conversión, puedes agregar a un puntero un número entero, pero no puedes agregar dos punteros...).

En Java, no existe aritmética de punteros, pero alguien aún encontró concatenación de cadenas sin la + El operador sería lo suficientemente ridículo como para justificar una excepción en el credo de "la sobrecarga del operador es mala".

Es sólo que usted, como C (por razones históricas) o Java (por razones personales, ver más abajo) codificador, no puedes proporcionar el tuyo propio.

En C++, la sobrecarga de operadores no es opcional...

En C++, la sobrecarga de operadores para tipos integrados no es posible (y esto es algo bueno), pero usuario definido tipos pueden tener usuario definido sobrecargas del operador.

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

La verdad es que, al igual que el toString(), clone(), equals() Los métodos son para Java (es decir.casi estándar), la sobrecarga de operadores de C++ es una parte tan importante de C++ que se vuelve tan natural como los operadores de C originales o los métodos Java antes mencionados.

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

...pero no se debe abusar de él

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

La sobrecarga de conversión puede ser muy peligrosa porque puede generar ambigüedades.Por tanto, deberían reservarse para casos bien definidos.Como para && y ||, nunca los sobrecargues 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...De acuerdo...Entonces, ¿por qué no es posible en Java?

Porque James Gosling lo dijo:

Dejé de lado la sobrecarga del operador como elección bastante personal porque había visto a mucha gente abusar de él en C++.

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

Compare el texto de Gosling arriba con el de Stroustrup a continuación:

Muchas decisiones de diseño de 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 pensé que tenía derecho a imponer mis puntos de vista a los demás..

Bjarne Stroustrup.Fuente:El diseño y 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 de operadores (tipos concretos o numéricos, como BigDecimal, números complejos, matrices, contenedores, iteradores, comparadores, analizadores, etc.).

En C++, puedes beneficiarte de este beneficio gracias a la humildad de Stroustrup.En Java, simplemente estás jodido por culpa de Gosling. Decisión personal.

¿Se podría agregar a Java?

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

Así que no contengas la respiración esperando esta función...

¡¡¡Pero lo hacen en C#!!!

Sí...

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

Aparentemente, la gente de C#, con su "Todo primitivo es un struct, y un struct deriva del Objeto", acertó en el primer intento.

Y lo hacen en otros idiomas!!!

A pesar de todo el FUD contra la sobrecarga de operadores definidos usados, los siguientes lenguajes lo admiten: escala, Dardo, Pitón, F#, C#, D, Algol 68, Charla, maravilloso, Perla 6, C ++, Rubí, Haskell, MATLAB, eiffel, lua, Clojure, Fortran 90, Rápido, ada, Delfos 2005...

Tantos lenguajes, tantas filosofías diferentes (y a 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:

"Existe este principio sobre la mudanza, cuando te mudas de un apartamento a otro.Un experimento interesante es empacar su apartamento y poner todo en cajas, luego mudarse al siguiente apartamento y no desempacar nada hasta que lo necesite.Entonces estás preparando tu primera comida y estás sacando algo de una caja.Luego, después de aproximadamente un mes, has usado eso para descubrir qué cosas en tu vida realmente necesitas, y luego tomas el resto de las cosas (olvidas cuánto te gusta o qué tan genial es) y simplemente tíralo.Es sorprendente cómo esto simplifica tu vida y puedes usar ese principio en todo tipo de cuestiones de diseño:No hagas las cosas sólo porque son geniales o simplemente porque son interesantes".

Puedes leer el contexto de la cita aquí

Básicamente, la sobrecarga de operadores 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 función en C++ por parte de los desarrolladores que sobrecargaron operadores como '&&', '||', los operadores de conversión y, por supuesto, 'nuevo'.La complejidad resultante de combinar esto con pase por valor y excepciones está bien cubierta en el C++ excepcional libro.

Echa un vistazo a Boost.Units: Texto del enlace

Proporciona un análisis dimensional sin gastos generales mediante la sobrecarga del operador.¿Cuánto más claro puede llegar a ser esto?

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

en realidad generaría "Energía = 4 J", lo cual es correcto.

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

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

maravilloso tiene sobrecarga de operadores y se ejecuta en la JVM.Si no le importa el impacto en el rendimiento (que disminuye cada día).Es automático según los nombres de los métodos.por ejemplo, '+' llama al método 'más (argumento)'.

Creo que esto puede haber sido una elecció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 los operadores con funcionalidades que a menudo no tendrían relación con la naturaleza comúnmente aceptada del operador dado, haciendo casi imposible determinar qué hace un fragmento de código sin mirar la definición del operador.

Bueno, realmente puedes dispararte en el pie si el operador se sobrecarga.Es como con los punteros, la gente comete errores estúpidos y por eso se decidió quitarles las tijeras.

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

Decir que la sobrecarga de operadores conduce a errores lógicos del tipo que el operador no coincide 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?¿¡Dejar caer la capacidad de uso de funciones!?Esta es una respuesta cómica: "Inapropiada 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 programación respetable, y aquellos que piensan que no es seguro, oye, no, ambos dicen que tienes que usarla.Tomemos el C#.Dejaron caer los punteros, pero bueno, hay una declaración de "código inseguro": programe como desee bajo su propio riesgo.

Algunas personas dicen que la sobrecarga de operadores en Java provocaría confusión.¿Alguna vez esas personas se han detenido a mirar algún código Java haciendo algunos cálculos básicos como aumentar un valor financiero en un porcentaje usando BigDecimal?....la verbosidad de tal ejercicio se convierte en su propia demostración de ofuscación.Irónicamente, agregar la sobrecarga de operadores a Java nos permitiría crear nuestra propia clase de Moneda, lo que haría que dicho código matemático fuera elegante y simple (menos confuso).

Técnicamente, existe una sobrecarga de operadores en cada lenguaje de programación que puede manejar diferentes tipos de números, p.números enteros y reales.Explicación:El término sobrecarga significa que simplemente hay varias implementaciones para una función.En la mayoría de los lenguajes de programación se proporcionan diferentes implementaciones para el operador +, una para números enteros y otra para reales, esto se denomina sobrecarga de operadores.

Ahora bien, a muchas personas les resulta extraño que Java tenga una sobrecarga de operadores para el operador + para sumar cadenas, y desde un punto de vista matemático esto sería realmente extraño, pero visto desde el punto de vista del desarrollador de un lenguaje de programación, no hay nada de malo en agregar una sobrecarga de operadores integrada. para el operador + para otras clases, p.e.Cadena.Sin embargo, la mayoría de la gente está de acuerdo en que una vez que agrega la sobrecarga incorporada para + para String, generalmente es una buena idea proporcionar esta funcionalidad también al desarrollador.

Estoy completamente en desacuerdo con la falacia de que la sobrecarga del operador confunde el código, ya que esto queda en manos del desarrollador.Es ingenuo pensar esto y, para ser sincero, se está volviendo obsoleto.

+1 para agregar sobrecarga de operadores en Java 8.

Suponiendo que Java sea el lenguaje de implementación, entonces a, b y c serían referencias al tipo Complejo con valores iniciales nulos.Suponiendo también que Complex es inmutable como lo mencionado Gran entero y similares inmutables GranDecimal, Creo que te refieres a lo siguiente, ya que estás asignando la referencia al Complejo devuelto al agregar b y c, y no comparando esta referencia con a.

No es:

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

mucho más simple que:

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

A veces sería bueno tener sobrecarga de operadores, clases amigas y herencia múltiple.

Sin embargo, sigo pensando que fue una buena decisión.Si Java hubiera tenido una sobrecarga de operadores, entonces nunca podríamos estar seguros del significado de los operadores sin revisar el código fuente.Actualmente eso no es necesario.Y creo que su ejemplo sobre el uso de métodos en lugar de sobrecargar operadores también es bastante legible.Si desea dejar las cosas más claras, siempre puede agregar un comentario encima de las declaraciones peliagudas.

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

Esta no es una buena razón para rechazarlo, pero sí una razón práctica:

La gente no siempre lo usa de manera responsable.Mire este ejemplo de la biblioteca de Python scapy:

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

Aquí está la explicación:

El operador / se ha utilizado como operador de composición entre dos capas.Al hacerlo, la capa inferior puede tener uno o más de sus campos predeterminados sobrecargados de acuerdo con la capa superior.(Todavía puede dar el valor que desea).Se puede utilizar una cuerda como capa sin procesar.

Alternativas al soporte nativo de sobrecarga de operadores Java

Dado que Java no tiene sobrecarga de operadores, aquí hay algunas alternativas que puede considerar:

  1. Utilice otro idioma.Ambos maravilloso y escala tienen sobrecarga de operadores y están basados ​​en Java.
  2. Usar java-oo, un complemento que permite la sobrecarga de operadores en Java.Tenga en cuenta que NO es independiente de la plataforma.Además, tiene muchos problemas y no es compatible con las últimas versiones de Java (es decir,Java 10).(Fuente original de StackOverflow)
  3. Usar JNI, Interfaz nativa de Java o alternativas.Esto le permite escribir métodos C o C++ (¿tal vez otros?) para usar en Java.Por supuesto, esto tampoco es independiente de la plataforma.

Si alguien conoce otros, por favor comente y los agregaré a esta lista.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top