@Override hashCode()

Hola, buenos días, alguien podría explicarme el porqué de la necesidad de reescribir el metodo hascode() de los objetos si van hacer guardados en un hashset y si su modificación sigue algún patrón en especial y ademas también es necesario el cambio del método equals, se es que básico , pero no he encontrado información suficientemente clara en Internet.

Opciones de visualización de comentarios

Seleccione la forma que prefiera para mostrar los comentarios y haga clic en «Guardar las opciones» para activar los cambios.
Imagen de ezamudio

HashSet y HashMap

El hashCode() es importante para objetos que van a ser guardados en HashSet, o si van a ser usados como llaves en un HashMap, porque internamente esas colecciones usan el hashCode() de los objetos para mantenerlos ordenados.

El hashCode() no necesariamente debe ser único; si varios objetos tienen el mismo hashCode simplemente se agrupan juntos y el algoritmo de búsqueda al llegar a ese grupo, ya busca con el equals. Pero es mucho más rápido llegar primero por hashCode, porque es un vil entero y comparar enteros es de lo más rápido que puede hacer una computadora.

El equals() también es importante tenerlo porque se va a usar para cuando un HashSet o HashMap tenga dos objetos con el mismo hashCode. Si no lo sobreescribes, se usará la implementación de Object que compara si son realmente la misma instancia. Si eso te sirve, pues qué bien. Pero si tienes un caso en que tienes un objeto en un HashSet o como llave de un HashMap y te pasan otra instancia con los mismos valores para preguntar set.contains() o map.get(), entonces necesitas tener el equals() porque de otra forma podrían obtener una respuesta errónea (que el set o el map te digan que no contienen el objeto cuando realmente sí tienen uno igual).

Comportamiento por default y especialización

A ver si me sale una respuesta sencilla ( luego echo mucho choro )

Acá te va una descripción del comportamiento por default, como escribir equals y hashCode y porque es importante.

Lo que escribió Ezamudio es correcto y dicho en menos palabras. Si encuentras que la semana que viene tienes la misma pregunta quizá convenga que le dediques más tiempo que veas los ejemplos que siguen.

Comportamiento default

Estos métodos están definidos por la clase Object que es de donde heredan todos correcto? Como la clase object no puede saber como vas a crear una subclase de ella define estos dos métodos más o menos así:

public boolean equals( Object aOtro ) {
    return this == aOtro;
}
public int hashCode() {
    return this.internalNumberXyz;
}

Es decir el equals dice true, solo si es exactamente el mismo valor de referencia ejemplo:

Object a = new Object();
Object b = a;
assert a.equals( b );

Como a y b "apuntan" al mismo objeto pues "==" regresa true. Lo siguiente sería false:

Object c = new Object();
Object d = new Object();
assert c.equals( d)  == false ; // o assert ! c.equals(d );  

Es claro que aquí c y d apuntan a objetos diferentes no? Bueno hasta aquí todo claro espero respecto a equals ( rayos ya no me quedo corto el ejemplo )

Sobre hashCode ese internalNumberXyz se puede pensar como un numero consecutivo que la JVM le asigna a un objeto, o se se prefiere ( y se tiene familiaridad con C ) se puede pensar que es la dirección de memoria del objeto. No es necesariamente ninguna de las dos, basta con pensar que dos objetos diferentes de la misma clase no van a tener el mismo número.

Especialización

Cuando se crea una subclase de Object ( básicamente siempre que se define una clase ) se tiene la opción de definir CUANDO un objeto es igual a otro.

Por ejemplo si ponemos una clase Moneda:

class Moneda /*extends Object agregado por el compilador */ {
   int valor;// de 1 de 5, de 10 de 15??
   String codigo; // MXN?, USD?, EUR?
}

Podriamos definir cuando un peso es igual a otro así

// escribiendolo con más "correctes"
final class Moneda {
   private final int valor;
   private final String codigo;

   // Constructor privado para forzar la creación via método
   private Moneda( int valor, String codigo ) {  
        this.valor = valor;
        this.codigo = codigo;
    }

   /**
    * Una moneda es igual a otro objeto siempre y cuando:
    *  El otro objeto tambien sea una moneda
    *  Y la otra moneda tenga el mismo valor
    * Y la otra moneda tenga el mismo código
    **/

   @Override
   public boolean equals( Objecto otra ) {
       return otro instanceof Moneda
                    && this.valor == ((Moneda) otra).valor)
                    && this.codigo.equals( ((Moneda) otra).codigo );
   }
   @Override
   //public int hasCode() { rayos!!
   public int hashCode() { // ahora si
       // El hashcode debe de ser un valor único por
       // objeto que represente lo mismo ( LEASE DE LA MISMA CLASE ).
       // debe de ser simétrico y ...
       //todo esto: http://download.oracle.com/javase/6/docs/api/java/lang/Object.html#hashCode()
      return this.valor * 33 + codigo.hashCode(); // No importa mucho porque por 33 pero es buena idea.
    }

   // Y finalmente una forma de crear monedas.
    public static Moneda valueOf( int valor, String codigo ) {
        // room for optimization here...
        return new Moneda( valor, codigo.toLowerCase() );// para que MXN, MxN, y mnx sean iguales.
    }
}

No importa mucho porque el valor * 33 + codigo.hashCode(), el chiste es que para la misma moneda con el mismo valor siempre de el mismo hashcode.

Con toda esta definición se hace muchísimo más fácil crear dos objetos separados que representen la misma moneda:

Moneda unPeso   = Moneda.valueOf(1, "MXN");
Moneda otroPeso = Moneda.valueOf( 1, "MXN");
assert unPeso.equals( otroPeso );// da true

Lo mismo con el hashCode:

...
assert unPeso.hashCode() == otroPeso.hashCode();

Bueno hasta aquí queda claro como es el comportamiento por omisión y como es el comportamiento cuando se especializa la clase ( cuando se hereda pues) pero aún no está claro porque es importante.

Uso en colecciones

La mayoría si no es que todas las clases del paquete java.util. basan su comportamiento en la definición de estos dos métodos , para funcionar correctamente, a veces en solo uno de ellos.

Por ejemplo el método contains de java.util.List regresa true solo cuando la comparación usando equals regresa true:

List<Moneda>  monedero = llenarMonedero();// regresa un montón de monedas.

// Tengo 100 pesos para pagar?
if( monedero.contains( Mondeda.valueOf( 100, "MXN") )) {
    // si si hay
}

Sin la sobreescritura de estos métodos siempre te regresaría false, porque equals nunca regresaría true.

Lo mismo pasa con, por ejemplo el HashMap ( adios definitivamente mi ejemplo corto ) , el hashmap utiliza el hashCode para almacenar un objeto, y cuando lo vas a buscar no recorre toda la estructura, va directamente a donde sabe que lo dejo. Es como un diccionario, donde cuando buscas una palabra no ojeas todas las paginas, sino que te vas directamente a la letra.

Map<Moneda, Dulce>  precios = new HashMap<Moneda,Dulce>();
// llenarlo con dulces de diferentes precios:
// de 5, de 10, de 15
precios.put( Moneda.valueOf(  1, MXN ), new Dulce("chicle"));
precios.put( Moneda.valueOf(  5, MXN ), new Dulce("pulparindo"));
precios.put( Moneda.valueOf( 15, MXN ), new Dulce("Magnum... ")); // bueno no se me ocurrio otra cosa

//... tiempo despues...

Dulce  deaPeso = precios.get( Moneda.valueOf(1, "MXN") );

Cuando se invoca el método "get", se calcula el hashCode de la moneda, en este caso la moneda que pasamos no es la misma con la que se guardo el dulce, es decir no es la misma instancia, aunque si representa el mismo valor. Como da el mismo hashCode entonces si nos regresa correctamente el dulce guardado ahí.

Si no se sobrescribieran estos métodos el ejemplo del la lista sería:

MalaMoneda x  = new MalaMoneda();

boolean estuvo = false;
for( MalaMoneda  y : monedero ) {
   if( x.getValor() == y.getValor() && x.getCodigo() == y.getCodigo() ) {
       estuvo = true;
       break;
    }
}

if( estuvo ) {
 ...
}

Es decir tendríamos que escribir la comparación nosotros mismos cada vez y una o dos veces no hace daño, pero cuando se tiene un programa grande estas comparaciones aparecen decenas o cientos de veces. El hashmap ni se podría usar, tendríamos que conservar la referencia a la llave con la que se guardo siempre con lo cual no nos serviría de nada.

Osease

Siempre ponlos, a menos claro que lo que desees es precisamente el comportamiento por omisión.

Espero que esto te ayude ( y a quien tuviera la misma pregunta ).

Imagen de gallark

Gracias a ambos, despues de

Gracias a ambos, despues de estas explicaciones, me ha quedado completamente claro el panorama. Saludos compañeros del foro.

Imagen de benek

¿Único?

// El hashcode debe de ser un valor único por objeto que represente lo mismo.
// debe de ser simétrico y ...

A lo mejor te entendí mal, pero si no, tu texto sugiere que el hashCode() es único por objeto, lo cuál no es así. Puede que varios objetos generen el mismo hashCode(), es totalmente válido y legal.

Imagen de ezamudio

hasCode

benek, es que su método que debe ser único es hasCode, no hashCode. Es otra cosa :)

El hashCode puede estar repetido en varios objetos. Pero el contrato establece que dos objetos que se consideran iguales por equals(), sí deben tener el mismo hashCode().

Ah si. El valor debe de ser

Ah si.

El valor debe de ser único por objeto que represente lo mismo ( lo que Oscar quizó decir es: ) "idealmente".

Pero si es correcto, dos objetos incluso de la misma clase pueden tener el mismo hashCode y diferente equals. Lo que si es importante es que dos objetos que den true para equals deben de regresar el mismo hashCode, ahi si no hay vuelta de hoja.

Gracias por la aclaración.

Va un ejemplo ( este si lo compilé ) :

import java.util.Map;
import java.util.HashMap;

class MapTest {
  public static void main( String ... args ) {
    Map<Key,String> map = new HashMap<Key,String>();
    map.put(new Key(1,1) ,"Hola");
    map.put(new Key(1,2) ,"Adios");
    System.out.println( map.get( new Key(1,1)));
    System.out.println( map.get( new Key(1,2)));
    System.out.println( map.get( new Key(1,3)));
  }
}
class Key {
  int a;//usado como hashCode
  int b;// los dos tienen que coincidir en equals
  public Key( int a , int b ) {
    this.a = a;
    this.b = b;
  }
  @Override
   public boolean equals( Object other ) {
    return ((Key)other).a == this.a && ((Key)other).b == this.b;
  }
  @Override
   public int hashCode() {
    return a;
  }
}

Salida:

Hola
Adios
null