¿Por qué C # y Java usan la igualdad de referencia como valor predeterminado para '=='?

32

He estado reflexionando durante un tiempo por qué Java y C # (y estoy seguro de que otros lenguajes) predeterminan la igualdad de referencia ==.

En la programación que hago (que ciertamente es solo un pequeño subconjunto de problemas de programación), casi siempre quiero igualdad lógica al comparar objetos en lugar de igualdad de referencia. Estaba tratando de pensar por qué ambos lenguajes tomaron esta ruta en lugar de invertirla y tener ==igualdad lógica y usarla .ReferenceEquals()como referencia de igualdad.

Obviamente, el uso de la igualdad de referencia es muy simple de implementar y ofrece un comportamiento muy consistente, pero no parece encajar bien con la mayoría de las prácticas de programación que veo hoy.

No deseo parecer ignorante de los problemas al tratar de implementar una comparación lógica, y que tiene que implementarse en cada clase. También me doy cuenta de que estos lenguajes fueron diseñados hace mucho tiempo, pero la pregunta general se mantiene.

¿Hay algún beneficio importante en el incumplimiento de esto que simplemente me estoy perdiendo, o parece razonable que el comportamiento predeterminado sea la igualdad lógica y, por defecto, la igualdad de referencia no existe una igualdad lógica para la clase?

Cremallera
fuente
3
¿Porque las variables son referencias? Dado que las variables actúan como punteros, tiene sentido que se comparen como tal
Daniel Gratzer
C # usa la igualdad lógica para los tipos de valor como las estructuras. Pero, ¿cuál debería ser la "igualdad lógica predeterminada" para dos objetos de diferentes tipos de referencia? ¿O para dos objetos donde uno es de un tipo A heredado de B? Siempre "falso" como para estructuras? ¿Incluso cuando tiene el mismo objeto referenciado dos veces, primero como A, luego como B? No tiene mucho sentido para mí.
Doc Brown
3
En otras palabras, ¿está preguntando por qué en C #, si anula Equals(), no cambia automáticamente el comportamiento de ==?
svick

Respuestas:

29

C # lo hace porque Java lo hizo. Java lo hizo porque Java no admite la sobrecarga del operador. Como la igualdad de valores debe redefinirse para cada clase, no puede ser un operador, sino que debe ser un método. OMI, esta fue una mala decisión. Es mucho más fácil escribir y leer a == bque a.equals(b), y mucho más natural para los programadores con experiencia en C o C ++, pero a == bcasi siempre está mal. Los errores del uso de ==donde .equalsse requería han desperdiciado miles de horas de programador.

Kevin Cline
fuente
77
Creo que hay tantos partidarios de la sobrecarga de operadores como detractores, por lo que no diría "fue una mala decisión" como una declaración absoluta. Ejemplo: en el proyecto C ++ en el que trabajo, hemos sobrecargado el ==de muchas clases y hace unos meses descubrí que algunos desarrolladores no sabían lo que ==realmente estaba haciendo. Siempre existe este riesgo cuando la semántica de alguna construcción no es obvia. La equals()notación me dice que estoy usando un método personalizado y que tengo que buscarlo en alguna parte. En pocas palabras: creo que la sobrecarga del operador es un problema abierto en general.
Giorgio
99
Yo diría que Java no tiene sobrecarga de operador definida por el usuario . Muchos operadores tienen significados dobles (sobrecargados) en Java. Mire, +por ejemplo, qué suma (de valores numéricos) y concatenación de cadenas al mismo tiempo.
Joachim Sauer
14
¿Cómo puede a == bser más natural para los programadores con experiencia en C, ya que C no admite la sobrecarga del operador definida por el usuario? (Por ejemplo, la forma C de comparar cadenas es strcmp(a, b) == 0, no a == b.)
svick
Esto es básicamente lo que pensé, pero pensé que les pediría a aquellos con más experiencia que se aseguren de que no me falta algo obvio.
Cremallera
44
@svick: en C, no hay ningún tipo de cadena ni ningún tipo de referencia. Las operaciones de cadena se realizan a través de char *. Me parece obvio que comparar dos punteros para la igualdad no es lo mismo que una comparación de cadenas.
Kevin Cline
15

La respuesta corta: consistencia

Sin embargo, para responder adecuadamente a su pregunta, sugiero que demos un paso atrás y veamos la cuestión de qué significa igualdad en un lenguaje de programación. Existen al menos TRES posibilidades diferentes, que se utilizan en varios idiomas:

  • Igualdad de referencia : significa que a = b es verdadero si a y b se refieren al mismo objeto. No sería cierto si a y b se refieren a objetos diferentes, incluso si todos los atributos de a y b fueran los mismos.
  • Igualdad superficial : significa que a = b es verdadero si todos los atributos de los objetos a los que se refieren ayb son idénticos. La igualdad superficial se puede implementar fácilmente mediante una comparación bit a bit del espacio de memoria que representa los dos objetos. Tenga en cuenta que la igualdad de referencia implica igualdad superficial
  • Igualdad profunda : significa que a = b es verdadero si cada atributo en a y b es idéntico o profundamente igual. Tenga en cuenta que la igualdad profunda está implícita tanto en la igualdad de referencia como en la igualdad superficial. En este sentido, la igualdad profunda es la forma más débil de igualdad y la igualdad de referencia es la más fuerte.

Estos tres tipos de igualdad a menudo se usan porque son convenientes de implementar: un compilador puede generar fácilmente las tres comprobaciones de igualdad (en el caso de una igualdad profunda, el compilador podría necesitar usar bits de etiqueta para evitar bucles infinitos si una estructura ser comparado tiene referencias circulares). Pero hay otro problema: ninguno de estos podría ser apropiado.

En sistemas no triviales, la igualdad de objetos a menudo se define como algo entre la igualdad profunda y la igualdad de referencia. Para verificar si queremos considerar dos objetos como iguales en un determinado contexto, podríamos requerir que se comparen algunos atributos por su posición en la memoria y otros por una profunda igualdad, mientras que algunos atributos pueden ser algo completamente diferente. Lo que realmente nos gustaría es un "cuarto tipo de igualdad", uno realmente agradable, a menudo llamado en la literatura igualdad semántica . Las cosas son iguales si son iguales, en nuestro dominio. =)

Para que podamos volver a su pregunta:

¿Hay algún beneficio importante en el incumplimiento de esto que simplemente me estoy perdiendo, o parece razonable que el comportamiento predeterminado debería ser la igualdad lógica, y volver a la igualdad de referencia si no existe una igualdad lógica para la clase?

¿Qué queremos decir cuando escribimos 'a == b' en cualquier idioma? Idealmente, siempre debería ser lo mismo: igualdad semántica. Pero eso no es posible.

Una de las principales consideraciones es que, al menos para tipos simples como números, esperamos que dos variables sean iguales después de la asignación del mismo valor. Vea abajo:

var a = 1;
var b = a;
if (a == b){
    ...
}
a = 3;
b = 3;
if (a == b) {
    ...
}

En este caso, esperamos que 'a sea igual a b' en ambas declaraciones. Cualquier otra cosa sería una locura. La mayoría (si no todos) de los idiomas siguen esta convención. Por lo tanto, con tipos simples (también conocidos como valores) sabemos cómo lograr la igualdad semántica. Con los objetos, eso puede ser algo completamente diferente. Vea abajo:

var a = new Something(1);
var b = a;
if (a == b){
    ...
}
b = new Something(1);
a.DoSomething();
b.DoSomething();
if (a == b) {
    ...
}

Esperamos que el primer 'si' siempre sea cierto. Pero, ¿qué esperas del segundo 'si'? Realmente depende ¿Puede 'DoSomething' cambiar la igualdad (semántica) de ayb?

El problema con la igualdad semántica es que el compilador no puede generarlo automáticamente para los objetos, ni es obvio a partir de las asignaciones . Se debe proporcionar un mecanismo para que el usuario defina la igualdad semántica. En lenguajes orientados a objetos, ese mecanismo es un método heredado: igual . Al leer un fragmento de código OO, no esperamos que un método tenga la misma implementación exacta en todas las clases. Estamos acostumbrados a la herencia y la sobrecarga.

Con los operadores, sin embargo, esperamos el mismo comportamiento. Cuando vea 'a == b', debe esperar el mismo tipo de igualdad (de los 4 anteriores) en todas las situaciones. Por lo tanto, para lograr la coherencia, los diseñadores de idiomas utilizaron la igualdad de referencia para todos los tipos. No debe depender de si un programador ha anulado un método o no.

PD: El lenguaje Dee es ligeramente diferente de Java y C #: el operador igual significa igualdad superficial para tipos simples e igualdad semántica para clases definidas por el usuario (con la responsabilidad de implementar la operación = que corresponde al usuario; no se proporciona ningún valor predeterminado). Como, para los tipos simples, la igualdad superficial siempre es igualdad semántica, el lenguaje es consistente. Sin embargo, el precio que paga es que el operador igual no está definido por defecto para los tipos definidos por el usuario. Tienes que implementarlo. Y, a veces, eso es aburrido.

Hbas
fuente
2
When you see ‘a == b’ you should expect the same type of equality (from the 4 above) in all situations.Los diseñadores de lenguaje de Java utilizaron la igualdad de referencia para objetos y la igualdad semántica para primitivas. No es obvio para mí que esta fue la decisión correcta, o que esta decisión es más "consistente" que permitir ==que se sobrecargue por la igualdad semántica de los objetos.
Charles Salvia
También usaron "el equivalente de la igualdad de referencia" para las primitivas. Cuando usa "int i = 3" no hay punteros para el número, por lo que no puede usar referencias. Con las cadenas, un tipo de tipo "primitivo", es más evidente: debe usar ".intern ()" o una asignación directa (String s = "abc") para usar == (igualdad de referencia).
Hbas
1
PD: C #, por otro lado, no era consistente con sus cadenas. Y en mi humilde opinión, en este caso, eso es mucho mejor.
Hbas
@CharlesSalvia: en Java, si ay bson del mismo tipo, la expresión a==bprueba si ay si btiene la misma cosa. Si uno de ellos tiene una referencia al objeto # 291, y el otro tiene una referencia al objeto # 572, no tienen la misma cosa. El contenido de los objetos # 291 y # 572 puede ser equivalente, pero las variables en sí contienen cosas diferentes.
supercat
2
@CharlesSalvia Está diseñado de tal manera que puedes ver a == by saber lo que hace. Del mismo modo, puede ver a.equals(b)y presumir que se sobrecarga equals. Si se a == bllama a.equals(b)(si se implementa), ¿se compara por referencia o por contenido? ¿No te acuerdas? Debe verificar la clase A. El código ya no es tan rápido de leer si ni siquiera está seguro de cómo se llama. Sería como si se permitieran métodos con la misma firma, y ​​el método que se llama depende de cuál sea el alcance actual. Tales programas serían imposibles de leer.
Neil
0

Estaba tratando de pensar por qué ambos lenguajes tomaron esta ruta en lugar de invertirla y tener == ser igualdad lógica y usar .ReferenceEquals () para igualdad de referencia.

Porque este último enfoque sería confuso. Considerar:

if (null.ReferenceEquals(null)) System.out.println("ok");

¿Debería imprimirse este código "ok", o debería arrojar un NullPointerException?

Atsby
fuente
-2

Para Java y C #, el beneficio radica en que están orientados a objetos.

Desde el punto de vista del rendimiento , el código más fácil de escribir también debería ser más rápido: dado que OOP pretende que los elementos lógicamente distintos sean representados por diferentes objetos, la verificación de la igualdad de referencia sería más rápida, teniendo en cuenta que los objetos pueden llegar a ser bastante grandes.

Desde un punto de vista lógico , la igualdad de un objeto con respecto a otro no tiene que ser tan obvia como la comparación de las propiedades de igualdad del objeto (por ejemplo, ¿cómo se interpreta lógicamente nulo == nulo? Esto puede diferir de un caso a otro).

Creo que todo se reduce a su observación de que "siempre desea igualdad lógica sobre igualdad de referencia". El consenso entre los diseñadores de idiomas fue probablemente lo contrario. Personalmente, me resulta difícil evaluar esto, ya que me falta el amplio espectro de experiencia en programación. Aproximadamente, uso la igualdad de referencia más en los algoritmos de optimización y la igualdad lógica más en el manejo de conjuntos de datos.

Rafael Emshoff
fuente
77
La igualdad de referencia no tiene nada que ver con la orientación a objetos. Todo lo contrario, en realidad: una de las propiedades fundamentales de la orientación a objetos es que los objetos que tienen el mismo comportamiento son indistinguibles. Un objeto debe poder simular otro objeto. (¡Después de todo, OO se inventó para la simulación!) La igualdad de referencia le permite distinguir entre dos objetos diferentes que tienen el mismo comportamiento, le permite distinguir entre un objeto simulado y uno real. Por lo tanto, la Igualdad de referencia rompe la orientación a objetos. Un programa OO no debe usar Igualdad de referencia.
Jörg W Mittag
@ JörgWMittag: Para hacer un programa orientado a objetos correctamente, se requiere que haya un medio para preguntarle al objeto X si su estado es igual al de Y [una condición potencialmente transitoria], y también un medio para preguntarle al objeto X si es equivalente a Y [X es equivalente a Y solo si se garantiza que su estado sea eternamente igual a Y]. Tener métodos virtuales separados para equivalencia e igualdad de estado sería bueno, pero para muchos tipos, la desigualdad de referencia implicará no equivalencia, y no hay razón para pasar tiempo en el envío de métodos virtuales para probarlo.
supercat
-3

.equals()compara variables por sus contenidos. en lugar de ==eso compara los objetos por su contenido ...

usar objetos es más preciso para usar .equals()

Nuno Dias
fuente
3
Su suposición es incorrecta. .equals () hace todo lo que .equals () fue codificado para hacer. Normalmente es por contenido, pero no tiene que ser así. Además, no es más preciso usar .equals (). Solo depende de lo que intentes lograr.
Cremallera