En Java, el operador == y el método equals() sirven para comparaciones, pero con diferencias clave. El operador == verifica si dos referencias apuntan a la misma instancia en memoria, es decir, si son idénticas en ubicación. Por otro lado, el método equals() está diseñado para comparar el contenido o estado lógico de los objetos, lo que permite evaluar igualdad basada en atributos específicos.
Considere el siguiente ejemplo con objetos String:
String primera = new String("ejemplo");
String segunda = new String("ejemplo");
System.out.println(primera.equals(segunda)); // Salida: true
System.out.println(primera == segunda); // Salida: false
Aquí, equals() devuelve true porque ambas cadenas tienen el mismo contenido, mientras que == devuelve false ya que se crearon como instancias separadas en el heap.
Todas las clases en Java heredan de la clase base Object, que proporciona una implementación predeterminada de equals():
public boolean equals(Object obj) {
return (this == obj);
}
Esta implementación predeterminada utiliza el operador ==, por lo que, sin sobrescritura, ambos métodos son equivalentes y comparan direcciones de memoria. Sin embargo, muchas clases, como String, sobrescriben equals() para ofrecer una comparación de contenido más flexible.
En la clase String, el método equals() ha sido optimizado para distintas versiones de JDK. Por ejemplo, en JDK 17, la implementación se basa en arrays de bytes para una eficiencia mejorada:
public boolean equals(Object otroObjeto) {
if (this == otroObjeto) {
return true;
}
if (otroObjeto instanceof String) {
String otraCadena = (String) otroObjeto;
if (coder() == otraCadena.coder()) {
return isLatin1() ? StringLatin1.equals(valor, otraCadena.valor)
: StringUTF16.equals(valor, otraCadena.valor);
}
}
return false;
}
Para el caso Latin1, la comparación se realiza así:
@HotSpotIntrinsicCandidate
public static boolean equals(byte[] valor, byte[] otro) {
if (valor.length == otro.length) {
for (int i = 0; i < valor.length; i++) {
if (valor[i] != otro[i]) {
return false;
}
}
return true;
}
return false;
}
Este enfoque primero verifica si los arrays tienen la misma longitud; si no, retorna false. Luego, compara cada byte individualmente hasta encontrar una discrepancia o confirmar la igauldad.
En comparación, JDK 8 utiliza una implementación más directa con arrays de caracteres:
public boolean equals(Object otroObjeto) {
if (this == otroObjeto) {
return true;
}
if (otroObjeto instanceof String) {
String otraCadena = (String) otroObjeto;
int longitud = valor.length;
if (longitud == otraCadena.valor.length) {
char[] caracteres1 = valor;
char[] caracteres2 = otraCadena.valor;
int indice = 0;
while (longitud-- != 0) {
if (caracteres1[indice] != caracteres2[indice])
return false;
indice++;
}
return true;
}
}
return false;
}
Aquí, el proceso es similar: se compara primero la longitud de los arrays de caracteres, y si coinciden, se itera caracter por caracter para determinar la igualdad.