2012-07-02 17 views
13

Tengo un hashmap que deseo copiar para otro uso. Pero cada vez que lo copio y lo reutilizo, también cambia el original. ¿Por qué es eso?Asignación de Hashmap a Hashmap

do { 
      Map<Integer, Map<String, Object>> map1 = originalMap; 
      //at the second iteration originalMap is the same as map1 of the last iteration, 
      //eventhough the change was nog accepted; 
      //do something with map1 (change value); 
      if(change is accepted) { 
       originalMap = map1; 
      } 
     } while(iteration < 10); 

Gracias de antemano

public static <Integer,String, Schedule>Map<Integer, Map<String, Schedule>> deepCopy(Map<Integer, Map<String, Schedule>> original) { 
    Map<Integer, Map<String, Schedule>> copy = new HashMap<Integer, Map<String, Schedule>>(); 

    for (Map.Entry<Integer, Map<String, Schedule>> entry : original.entrySet()) { 
     copy.put(entry.getKey(), deepCopy2(entry.getValue())); 
    } 
    return copy; 
} 

public static <String, Schedule>Map<String, Schedule> deepCopy2(Map<String, Schedule> original) { 
    Map<String, Schedule> copy = new HashMap<String, Schedule>(); 
    for (Map.Entry<String, Schedule> entry : original.entrySet()) { 
     copy.put(entry.getKey(), entry.getValue()); 
    } 

    return copy; 
} 
+11

porque no es una copia del 'HashMap' sino una _ referencia_ al' HashMap', lo que significa que cualquier cambio en uno afectará al otro. Necesita realizar una _deep_ copia del 'HashMap' –

Respuesta

47

Lo que se hizo fue no crear una copia del mapa, sino de la referencia a ella. cuando dos referencias apuntan al mismo objeto, los cambios a uno se reflejarán en el otro.

Solución 1: Si esto fuera un mapa de algún tipo sencillo a otro, que haría esto en su lugar:

Map<SomeType, OtherType> map1 = new HashMap<SomeType, OtherType>(original); 

Esto se llama un Copy Constructor. Casi todas las implementaciones estándar de Collection y Map tienen una, y por lo general es la forma más sencilla de clonar una estructura simple. Esto funciona bien siempre y cuando SomeType y OtherType son immutable (por ejemplo Integer y otros tipos Number, Boolean, String, pero no Colecciones, fechas, mapas, etc.) Matrices

Si no es así, como otras que responden y los comentaristas tienen señaló, también necesita copiar los valores del mapa.

Solución 2: Aquí hay una versión rápida y sucia que debe ser seguro:

Map<Integer, Map<String, Object>> original=new HashMap<Integer, Map<String,Object>>(); 
Map<Integer, Map<String, Object>> copy = 
     new HashMap<Integer, Map<String, Object>>(); 
for(Entry<Integer, Map<String, Object>> entry : original.entrySet()){ 
    copy.put(entry.getKey(), new HashMap<String, Object>(entry.getValue())); 
} 

Pero, en realidad, me gusta la idea de Hunter de proporcionar un método de copia de profundidad. Así que aquí está la solución de 3: mi propia versión utilizando parámetros genéricos:

public static <K1, K2, V> Map<K1, Map<K2, V>> deepCopy(
    Map<K1, Map<K2, V>> original){ 

    Map<K1, Map<K2, V>> copy = new HashMap<K1, Map<K2, V>>(); 
    for(Entry<K1, Map<K2, V>> entry : original.entrySet()){ 
     copy.put(entry.getKey(), new HashMap<K2, V>(entry.getValue())); 
    } 
    return copy; 
} 

se le puede llamar así:

Map<Integer, Map<String, Object>> original=new HashMap<Integer, Map<String,Object>>(); 
// do stuff here 
Map<Integer, Map<String, Object>> copy = deepCopy(original); 

actualización

He hackeado una clase que realiza una clonación profunda para Mapas, Colecciones y Matrices (primitiva y de otro tipo). Uso:

Something clone = DeepClone.deepClone(original); 

Aquí está:

public final class DeepClone { 

    private DeepClone(){} 

    public static <X> X deepClone(final X input) { 
     if (input == null) { 
      return input; 
     } else if (input instanceof Map<?, ?>) { 
      return (X) deepCloneMap((Map<?, ?>) input); 
     } else if (input instanceof Collection<?>) { 
      return (X) deepCloneCollection((Collection<?>) input); 
     } else if (input instanceof Object[]) { 
      return (X) deepCloneObjectArray((Object[]) input); 
     } else if (input.getClass().isArray()) { 
      return (X) clonePrimitiveArray((Object) input); 
     } 

     return input; 
    } 

    private static Object clonePrimitiveArray(final Object input) { 
     final int length = Array.getLength(input); 
     final Object copy = Array.newInstance(input.getClass().getComponentType(), length); 
     // deep clone not necessary, primitives are immutable 
     System.arraycopy(input, 0, copy, 0, length); 
     return copy; 
    } 

    private static <E> E[] deepCloneObjectArray(final E[] input) { 
     final E[] clone = (E[]) Array.newInstance(input.getClass().getComponentType(), input.length); 
     for (int i = 0; i < input.length; i++) { 
      clone[i] = deepClone(input[i]); 
     } 

     return clone; 
    } 

    private static <E> Collection<E> deepCloneCollection(final Collection<E> input) { 
     Collection<E> clone; 
     // this is of course far from comprehensive. extend this as needed 
     if (input instanceof LinkedList<?>) { 
      clone = new LinkedList<E>(); 
     } else if (input instanceof SortedSet<?>) { 
      clone = new TreeSet<E>(); 
     } else if (input instanceof Set) { 
      clone = new HashSet<E>(); 
     } else { 
      clone = new ArrayList<E>(); 
     } 

     for (E item : input) { 
      clone.add(deepClone(item)); 
     } 

     return clone; 
    } 

    private static <K, V> Map<K, V> deepCloneMap(final Map<K, V> map) { 
     Map<K, V> clone; 
     // this is of course far from comprehensive. extend this as needed 
     if (map instanceof LinkedHashMap<?, ?>) { 
      clone = new LinkedHashMap<K, V>(); 
     } else if (map instanceof TreeMap<?, ?>) { 
      clone = new TreeMap<K, V>(); 
     } else { 
      clone = new HashMap<K, V>(); 
     } 

     for (Entry<K, V> entry : map.entrySet()) { 
      clone.put(deepClone(entry.getKey()), deepClone(entry.getValue())); 
     } 

     return clone; 
    } 
} 
+0

preguntándose si clone() sería una buena forma de copiar el HashMap? –

+0

@Louis hecho. Alexey, dudo que el clon haga la copia profunda según sea necesario. –

+2

'clone()' casi nunca es una buena idea (elemento eficaz de Java 11), pero específicamente no va a hacer el trabajo en este caso. –

3

Al hacer esto:

Map<Integer, Map<String, Object>> copy = originalMap; 

... eres no copiar el mapa, sólo se crea una nueva variable que se refiere al mismo mapa exacto, y claramente los cambios que realice al usar esta variable se reflejarán en el mapa original; apuntan al mismo objeto en la memoria.Mejor copiar el mapa original utilizando el constructor que recibe otro mapa como un parámetro:

Map<Integer, Map<String, Object>> copy; 
copy = new HashMap<Integer, Map<String, Object>>(originalMap); 

El código anterior creará una copia superficial del mapa original, es decir: si cambia el valor de los elementos dentro de un mapa, los cambios se reflejarán en el otro, pero puede agregar/eliminar libremente los elementos de cualquier mapa y el otro no se verá afectado. Si eso no es lo suficientemente bueno, tendrá que realizar una copia en profundidad de los elementos en el mapa en el momento de copiarlo.

2

Una solución simple y sencilla sería simplemente un bucle sobre los valores en el mapa y copiarlos en un mapa:

Map<Integer, Map<String, Object>> map1; 

//iterate over the map copying values into new map 
for(Map.Entry entry : originalMap.entrySet()) 
{ 
    map1.put(entry.getKey(), new HashMap<String, Object>(entry.getValue())); 
} 

Una mejor solución sería la de terminar con esto en un método:

public static <K,J,V> Map<K, Map<J, V>> deepCopy(Map<K, Map<J, V>> original) 
{ 
    Map<K, Map<J, V>> copy; 

    //iterate over the map copying values into new map 
    for(Map.Entry<K, Map<J, V>> entry : original.entrySet()) 
    { 
     copy.put(entry.getKey(), new HashMap<J, V>(entry.getValue())); 
    } 

    return copy; 
} 
+0

Acepto que el método sería una mejor idea (+1 para eso), pero agregaría comodines genéricos para hacerlo reutilizable (y hacerlo estático) –

+0

La edición estaba en progreso para el tipo de seguridad. También debería ser estático, buen punto –

+0

, así que ahora ambos tenemos el mismo método en nuestra respuesta :-) –

0

En su código, originalMap es simplemente una referencia a map1. Ahora, ambos apuntan a las mismas claves y valores. Recuerde, esto es Java donde '=' en referencias de objeto es simplemente una asignación de referencia (no una copia profunda o superficial).

Las colecciones Java generalmente admiten alguna forma de copia superficial a través de clone o putAll. En el caso de los mapas, asumiendo map1 y map2 son de tipo HashMap<KeyType,ValueType>, si quieres un mapa a ser una copia superficial del otro (es decir, un objeto HashMap distintos pero con claves y valores compartidos), esto se hace:

HashMap<KeyType,ValueType> map1(); 
HashMap<KeyType,ValueType> map2(); 

map2.put(x1,v1); // map2 = {{x1,v1}} 

map1.put(x2,v2); // map1 = {{x2,v2}} 

map1 = map2.clone(); // map1 = {{x1,v1}}, with x2 and v2 gone 

map2.clear(); 
map2.put(x3,v3); // map2 = {{x3,v3}} 
map2.put(x4,v4); // map2 = {{x3,v3},{x4,v4}} 

map1.put(x4,v5); // map1 = {{x1,v1}, {x4,v5}} 

// add all of map2 into map1, replacing any mappings with shared keys 
map1.putAll(map2); // map1 = {{x1,v1},{x3,v3},{x4,v4}}, notice how v5 is gone 

En un pensamiento de despedida, debe acostumbrarse a consultar la API de Java. Eso te ayudará mucho.

http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html

0

Esto podría estar llegando un poco tarde, pero otra solución sencilla habrá para serializar el mapa para un flujo de salida y de-serializar a un nuevo objeto de mapa. Esa es también una de las formas más fáciles de romper el patrón singleton.

+1

Sí, lo vi, pero tener las referencias en la etapa anterior fue bastante útil. Copiar con copia profunda es muy bueno, pero en este momento no funciona –