2011-06-05 12 views
7

estoy teniendo problemas para usar mi propia clase como una clave para un HashMap¿Por qué los objetos personalizados no son claves equivalentes para un HashMap?

public class ActorId { 
    private final int playerId; 
    private final int id; 

    ActorId(int playerId, int id) { 
     this.playerId = playerId; 
     this.id = id; 
    } 

    public boolean equals(ActorId other) { 
     return this.id == other.id && this.playerId == other.playerId; 
    } 

    public int hashCode() { 
     int hash = 1; 
     hash = hash * 31 + playerId; 
     hash = hash * 31 + id; 
     return hash; 
    } 

    public String toString() { 
     return "#" + playerId + "." + id; 
    } 

    public int getPlayerId() { 
     return playerId; 
    } 
} 

Aquí está una prueba unitaria no

import static org.junit.Assert.*; 
import java.util.Map; 
import org.junit.Test; 

public class ActorIdTest { 
    @Test 
    public final void testAsMapKey() { 
     ActorId a = new ActorId(123, 345); 
     ActorId b = new ActorId(123, 345); 

     assertTrue(a.equals(b)); 
     assertEquals(a.hashCode(), b.hashCode()); 

     // Works with strings as keys 
     Map<String, String> map1 = new java.util.HashMap<String, String>(); 

     map1.put(a.toString(), "test"); 
     assertEquals("test", map1.get(a.toString())); 
     assertEquals("test", map1.get(b.toString())); 
     assertEquals(1, map1.size()); 

     // But not with ActorIds 
     Map<ActorId, String> map2 = new java.util.HashMap<ActorId, String>(); 

     map2.put(a, "test"); 
     assertEquals("test", map2.get(a)); 
     assertEquals("test", map2.get(b)); // FAILS here 
     assertEquals(1, map2.size()); 

     map2.put(b, "test2"); 
     assertEquals(1, map2.size()); 
     assertEquals("test2", map2.get(a)); 
     assertEquals("test2", map2.get(b)); 
    } 
} 
+0

Dice que falla en '... map2.get (b)' - no tiene esa clave en su Mapa. Solo ha agregado un objeto al mapa, la instancia 'a'. –

+0

@ Björn Sí, los dos objetos ActorId son iguales y tienen el mismo código hash, por lo que deben devolver el mismo valor del mapa. – dlundquist

+0

¡Heh, lo siento! Acaba de salir de la cama, debería haber leído todo el bloque de códigos. –

Respuesta

9

necesita cambiar

public boolean equals(ActorId other) { 
    .... 
} 

a

public boolean equals(Object other) { 
    .... 
} 

Consejo del día: Siempre use la anotación @Override.

Si se hubiera usado la anotación @Override, el compilador habría cogido el error y dijo:

El método es igual a (actorId) de tipo actorId debe anular o poner en práctica un método supertipo

+1

@MGwynne: su comentario es engañoso. 1) la API de HashMap ** especifica ** que se usa 'igual (Objeto)'. 2) simplemente cambiar la firma de 'get' no cambiaría el comportamiento. Sería imposible implementar un método 'V get (K)' que realmente utilizara el método 'boolean T.equals (K)' sin pasar un objeto 'Clase ' y usar la reflexión para buscar y llamar al método' equals' . –

+0

@Stephen C - ¿me puede indicar dónde la API de HashMap especifica que utiliza iguales (Objeto), que no sea la firma de tipo? Hasta donde yo sé, el método get simplemente llama al método igual en los objetos. Dudo mucho que específicamente los obligue a oponerse, ya que esto ya viene dado por la firma del tipo. El único punto es que el método get especifica * en la firma de tipo * que toma un objeto y, por lo tanto, el método de sobrecarga sobrecargado está oculto. Si la firma se cambiara a 'V get (clave K)' entonces funcionaría como se esperaba @dlundquist. ¿Estoy malentendiendo algo? – MGwynne

+0

Por supuesto, no puede ir y cambiar esto fácilmente, ni lo estaba sugiriendo. También hay buenas razones, basadas en el concepto de igualdad de objetos de Java, para la firma que toma un objeto, solo estaba tratando de señalar por qué el método ActorId igual no se llamó, aunque uno podría ingenuamente pensar que lo sería. – MGwynne

3

Su código es correcto, pero también debe anular el método equals heredado de Object.

Agregue esto a su clase ActorId:

@Override 
public boolean equals(Object other) { 
    if(other == null || other.getClass() != getClass()) 
     return false; 
    return equals((ActorId)other); 
} 
1

Definitivamente debe reemplazar el método equals (Object), y por cierto implementación de un mapa (HashMap) también es NECESARIO que overrdide el método hashCode ()

Tuve el mismo problema, y ​​sin la implementación personalizada de hashCode, nunca se llamó al método igual de la clase "ActorId".

0

Por defecto Java invoca booleano equals(Object obj); Por lo tanto, de hacer login es correcto, pero si desea sobrescribir equals() utiliza objetos como parámetro y comprueba la clase y por instanceOf o getClass() y hacer un casting clase.

if (obj instanceOf ActorId) { 
    ActorId other = (ActorId)obj; 
    ... compare fields 
} 
Cuestiones relacionadas