Copia superficial de un mapa en Java

106

Según tengo entendido, hay un par de formas (tal vez otras también) de crear una copia superficial de a Mapen Java:

Map<String, Object> data = new HashMap<String, Object>();
Map<String, Object> shallowCopy;

// first way
shallowCopy = new HashMap<String, Object>(data);

// second way
shallowCopy = (Map<String, Object>) ((HashMap<String, Object>) data).clone();

¿Se prefiere una forma sobre la otra y, de ser así, por qué?

Una cosa que vale la pena mencionar es que la segunda forma muestra una advertencia de "Reparto sin marcar". Así que tienes que agregar @SuppressWarnings("unchecked")para evitarlo, lo cual es un poco irritante (ver más abajo).

@SuppressWarnings("unchecked")
public Map<String, Object> getDataAsMap() {
    // return a shallow copy of the data map
    return (Map<String, Object>) ((HashMap<String, Object>) data).clone();
}
dcp
fuente
En las versiones más recientes de Java (desde Java 10, para ser exactos) puede usar el método de fábrica estática Map.copyOf . ¡Pero tenga en cuenta que devuelve un mapa no modificable!
Oleksandr Pyrohov

Respuestas:

106

Siempre es mejor copiar usando un constructor de copias. clone()en Java está roto (ver SO: ¿Cómo anular correctamente el método de clonación? ).

Josh Bloch sobre diseño: constructor de copias versus clonación

Si ha leído el artículo sobre la clonación en mi libro, especialmente si lee entre líneas, sabrá que creo que cloneestá profundamente roto. [...] Es una pena que Cloneablese rompa, pero pasa.

Bloch (quien, por cierto, diseñó e implementó el marco de la Colección) incluso fue más allá al decir que solo proporciona el clone()método solo "porque la gente lo espera". En realidad, NO recomienda usarlo en absoluto.


Creo que el debate más interesante es si un constructor de copias es mejor que una fábrica de copias, pero esa es una discusión completamente diferente.

poligenelubricantes
fuente
1
Sí, esta es una de mis partes favoritas del libro.
polygenelubricants
1
No me gusta decir que el clon () está roto. Prefiero decir que la clonación fue una decisión de diseño terrible y puede dañarte mucho si no la usas correctamente. Además, es posible que nunca confíe en los métodos clone () de otras personas. Así que terminamos siendo similares, tratamos de evitarlo, pero no está roto.
santiagobasulto
4
¿No es necesario que sepa qué implementación de Map está copiando para usar el ctor de copia? Parece una limitación innecesaria.
Jon-Hanson
"que solo proporciona el método clone () solo" porque la gente lo espera "" - ¿fuente?
Adam Parkin
60

Ninguno de los dos: el constructor al que te refieres está definido para la implementación HashMap de un mapa (así como para otros) pero no para la interfaz del mapa en sí (por ejemplo, considera la implementación del proveedor de la interfaz del mapa: no encontrará ese constructor).

Por otro lado no es recomendable utilizar el clone()método, como explica Josh Bloch.

Con respecto a la interfaz del Mapa (y de su pregunta, en la que pregunta cómo copiar un Mapa, no un HashMap), debe usar Map # putAll () :

Copia todas las asignaciones del mapa especificado a este mapa (operación opcional). El efecto de esta llamada es equivalente al de llamar a put (k, v) en este mapa una vez para cada asignación de la clave k al valor v en el mapa especificado.

Ejemplo:

// HashMap here, but it works for every implementation of the Map interface
Map<String, Object> data = new HashMap<String, Object>();
Map<String, Object> shallowCopy = new HashMap<String, Object>();

shallowCopy.putAll(data);
Luca Fagioli
fuente
2
Entonces, para aclarar: si sabe que está copiando en una implementación Mapque tiene un constructor de copia, ¿no hay razón para no usar el constructor de copia, entonces?
Adam Parkin
2
Exactamente, e incluso puedes pensarlo al revés: si lo putAllusas no necesitas saber si la Mapimplementación que estás usando tiene un constructor de copia o no. Por lo tanto, un constructor de copia de cualquier Mapimplementación es redundante.
Luca Fagioli
1
Claro, aunque en general me gustan más los de 1 línea que los de 2. ;)
Adam Parkin
11

Copia un mapa sin conocer su implementación:

static final Map shallowCopy(final Map source) throws Exception {
    final Map newMap = source.getClass().newInstance();
    newMap.putAll(source);
    return newMap;
}
Terris
fuente
3
Considere agregar <K,V>parámetros de tipo para ayudar a garantizar la seguridad del tipo.
Barett
1
¿Qué pasa con los mapas sin constructores de argumento cero?
Isaac Saffold