2012-04-19 94 views
7

tengo el siguiente HashMap con propiedades claves y valores:Java: comparar HashMap <String, Object> si el valor podría ser un Object []

private HashMap<String, Object> prop_values; 

tengo que comprobar si una instancia de la misma es igual a otro. En el pasado, yo acabo de hacer esto:

if (prop_values_1.equals(prop_values_2)){ 
    // do something 
} 

Y esto funcionó hasta que llegué Object[] como un valor. Por lo tanto, mi expresión anterior siempre devolvió false en tal HashMap con cualquier valor Object[].

Por lo tanto, tengo que poner en práctica este método:

private boolean isPropValuesEquals(HashMap<String, Object> pv1, HashMap<String, Object> pv2){ 
    boolean isEquals = true; 

    if (pv1 == null || pv2 == null){ 
     return false; 
    } 

    if (!pv1.keySet().equals(pv2.keySet())){ 
     return false; 
    } 

    for (String key : pv1.keySet()){ 

     Object cur_pv1 = pv1.get(key); 
     Object cur_pv2 = pv2.get(key); 

     if (cur_pv1 instanceof Object[]){ 
     if (cur_pv2 instanceof Object[]){ 
      isEquals = Arrays.equals((Object[])cur_pv1, (Object[])cur_pv2); 
     } else { 
      return false; 
     } 
     } else { 
     isEquals = isEquals && cur_pv1.equals(cur_pv2); 
     } 

     if (!isEquals){ 
     return false; 
     } 
    } 

    return isEquals; 

} 

Funciona, pero parece que hay algún tipo de corte, y no estoy seguro de que esta es la mejor manera de lograr lo que necesito.

lo tanto, aquí hay dos cuestiones:.

  • qué Object [] iguales() no es el mismo que Arrays.equals()? Parece ser doloroso

  • ¿Existe alguna forma mejor de comparar HashMap<String, Object>, si los valores pueden ser Object[]?

+1

Use una lista en lugar de un Objeto [], y todo funcionará como espera que lo haga. –

+0

@JBNizet, hay otro tipo de problema con la advertencia de seguridad de tipo al convertir de 'Objeto' al tipo parametrizado como' Lista '. –

+0

Tiene advertencias, pero no es menos seguro que enviar contenido a Object []. Simplemente encapsula todo correctamente y asegúrate de tener un código robusto. –

Respuesta

3

El problema grave es que no hay manera de anular el equals() de una matriz. Por qué no está escrito como "elementos iguales en el mismo orden" en primer lugar, no tengo idea. Definitivamente podría haber sido (a menos que haya algún fundamento oscuro para no hacerlo; no puedo pensar en ninguno; si desea verificar la igualdad de referencia, usa ==, entonces, ¿qué dañaría equals()?).

Su solución es el camino a seguir. Sólo un par de detalles a tener en cuenta:

  • En lugar de x instanceof Object[], se puede usar x.getClass().isArray(), por lo que sería trabajar para otras matrices, así como int[] (que es no una subclase de Object[]). Desventaja: es posible que deba comprobar por separado si x es null.

  • Si las matrices pueden contener matrices anidadas, considere usar Arrays.deepEquals().

Una demostración de que primitivos matrices no son Object[] s:

Object a = new int[1]; 
    System.out.println("" + (a instanceof Object[])); // false 
    System.out.println("" + a.getClass().isArray()); // true 

Sin embargo, otro dolor en el culo es que incluso si usted encuentra que x es una matriz, que todavía tiene que manejar por separado casos para todos los diferentes tipos de elementos primitivos. No hay forma de manejarlos de forma genérica en el sistema de tipos de Java. Por supuesto, si no tiene arrays primitivos en nuestro mapa, entonces no necesita manejar este caso.

+1

Este es un aspecto ligeramente oscuro de Java, pero en realidad TODAS las matrices son 'instanceof Object []'! Por cierto, si 'igual' se implementó de otra manera que lo que es, creo que rompería más cosas que soluciones. Por un lado, se espera que las matrices sean un tipo de objeto crudo, extremadamente eficiente. –

+0

para mí '(new String [] {" foo "," bar "} instanceof Object [])' returns 'true', lo he probado antes de implementar este método. Entonces, 'String []' ** es ** una subclase de 'Object []'. ¿Podría por favor dar más detalles sobre esto? Tal vez no todas las versiones de Java tienen ese tipo de comportamiento. Y gracias por 'deepEquals()'. –

+0

Te puedo asegurar que ** todas las versiones ** de Java se comportan de esa manera. Cuando estudiaba Scala y su tipo de covarianza/contravarianza, una vez leí una cita de Gosling que decía que sí, que eran conscientes del hecho de que la covarianza de tipo tiene poco sentido para una matriz, pero lo hacían así para facilitar el tipo de caso que tenga, y también haga pasar cualquier matriz como argumento posible. –

0

La igualdad puede ser diferente: ¿es la misma referencia (igualdad de referencia)? ¿O tiene los mismos contenidos?

El problema con las matrices es que son mutables. equals y hashCode de un objeto siempre deben venir en pares, y el código hash no debe cambiar (de lo contrario, los objetos no se pueden encontrar en las tablas hash). Por lo tanto, el equals normal (y el hashCode) no pueden usar el contenido de las matrices, tienen que depender de otra propiedad (no mutable) de la matriz, que es la referencia.

+2

Nada prohíbe cambiar un hashCode. Por cierto, dos listas son iguales si contienen objetos iguales en el mismo orden. Lo mismo podría haber sido don with arrays, pero no lo ha hecho (y probablemente nunca lo hará, por razones de compatibilidad con versiones anteriores). –

+0

@JBNizet Agregue el objeto A a una colección hash. Si el hash de A cambia, estará en el cubo incorrecto y todas las operaciones en la colección hash fallarán para ese objeto (como "contiene", etc.). – Lucero

+0

Sí, pero eso no significa que A.hashCode tenga errores. Hace que su uso de A buggy. –

0
why Object[].equals() is not the same as Arrays.equals()? 
  • Object1.equals(Object2) es lo mismo que Object1 == Object2, es decir la comparación de las direcciones de los objetos (no se espera por una gran cantidad de desarrolladores, pero esto es lamentablemente cierto)
  • Arrays.equals (matriz1, matriz2) compara el contenido de las matrices.

Para el primer punto:
Esta es la más abstracta de equals() método, del mismo modo que todas las clases se extienden del objeto. String.equals(String) es el caso de este método sobrescrito donde trabaja como Arrays.equals(array1, array2) .En esta manera, se allanó el camino para otros desarrolladores para anularlo por sus efectos sean más flexibles.

Es mejor usar List<Object> en lugar de Object[], porque tiene todos los métodos listos y puede satisfacer para todos los propósitos.

+0

Sí, pero * ¿por qué? –

+1

Esto es lo que los desarrolladores de la comunidad java codificaron, estas son sus reglas – GingerHead

+0

Sí, pero * ¿por qué * lo codificaron de esta manera? ¿Hay alguna razón de ser, o es solo una decisión arbitraria? –

0

El método equals de la clase Object implementa la relación de equivalencia posible más exigente en los objetos; es decir, para cualquier valor de referencia no nulo x e y, este método devuelve verdadero si y solo si xey se refieren al mismo objeto (x == y tiene el valor verdadero).

0

Una aplicación agradable, sino también ineficiente es

  • copia el mapa
  • reemplazar las matrices con la lista envuelta
  • hacer la comparación utilizando .equals estándar(), esto va a funcionar porque la lista las envolturas harán un profundo profundo adecuado en la matriz subyacente.

Código

public static Map<String, Object> arraysToLists(Map<String, Object> map) { 
    Map<String, Object> copy = new HashMap<String, Object>(map); 
    for (Entry<String, Object> entry : copy.entrySet()) { 
     if (entry.getValue() instanceof Object[]) { 
      entry.setValue(Arrays.asList((Object[]) entry.getValue())); 
     } 
    } 
    return copy; 
} 

public static boolean mapCompare(Map<String, Object> map1, 
     Map<String, Object> map2) { 
    return arraysToLists(map1).equals(arraysToLists(map2)); 
} 

caso de prueba

public static void main(String[] args) { 
    Map<String, Object> testA = new HashMap<String, Object>(); 
    testA.put("foo", new Object[] { "1", "2" }); 
    Map<String, Object> testB = new HashMap<String, Object>(); 
    testB.put("foo", new Object[] { "1" }); 
    Map<String, Object> testC = new HashMap<String, Object>(); 
    testC.put("foo", new Object[] { "1", "2" }); 

    // demonstrate equals() broken with Object arrays 
    System.out.println(testA.equals(testB)); // expect false 
    System.out.println(testA.equals(testC)); // expect true 

    // demonstrate new function works OK 
    System.out.println(mapCompare(testA, testB)); // expect false 
    System.out.println(mapCompare(testA, testC)); // expect true 
} 

salida

false 
false 
false 
true 
0

Joonas es correcto, sin embargo, me refactorizar el código para ser el siguiente. Puede verificarlo usted mismo para encontrar las diferencias.

private static boolean isPropValuesEquals(Map<String, Object> pv1, Map<String, Object> pv2) { 
    if (pv1 == null || pv2 == null || pv1.size() != pv2.size()) 
     return false; 

    for (Map.Entry<String, Object> entry : pv1.entrySet()) { 
     Object cur_pv1 = entry.getValue(); 
     Object cur_pv2 = pv2.get(entry.getKey()); 

     if (cur_pv1 == null^cur_pv2 == null) // if exactly one is null they're "not equal" 
      return false; 

     if (cur_pv1 != null) { 
      if (cur_pv1.getClass().isArray()) { 
       if (Arrays.deepEquals((Object[]) cur_pv1, (Object[]) cur_pv2)) 
        return false; 
      } else { 
       if (!cur_pv1.equals(cur_pv2)) { 
        return false; 
       } 
      } 
     } 
    } 

    return true; 
} 
Cuestiones relacionadas