Copie superficielle d'une carte en Java

107

Si je comprends bien, il existe plusieurs façons (peut-être d'autres également) de créer une copie superficielle d'un 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();

Une voie est-elle préférée à l'autre, et si oui, pourquoi?

Une chose qui mérite d'être mentionnée est que la deuxième méthode donne un avertissement "Cast non coché". Il faut donc en rajouter @SuppressWarnings("unchecked")pour le contourner, ce qui est un peu irritant (voir ci-dessous).

@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
la source
Dans les nouvelles versions de Java (depuis Java 10, pour être exact), vous pouvez utiliser la méthode de fabrique statique Map.copyOf . Mais notez qu'il renvoie une carte non modifiable!
Oleksandr Pyrohov

Réponses:

106

Il est toujours préférable de copier en utilisant un constructeur de copie. clone()en Java est cassé (voir SO: Comment remplacer correctement la méthode de clonage? ).

Josh Bloch sur la conception - Constructeur de copie versus clonage

Si vous avez lu l'article sur le clonage dans mon livre, surtout si vous lisez entre les lignes, vous saurez que je pense qu'il cloneest profondément brisé. [...] C'est une honte qui Cloneableest cassée, mais ça arrive.

Bloch (qui d'ailleurs a conçu et implémenté le framework Collection) est même allé plus loin en disant qu'il ne fournit la clone()méthode que «parce que les gens l'attendent». Il ne recommande PAS du tout de l'utiliser.


Je pense que le débat le plus intéressant est de savoir si un constructeur de copie est meilleur qu'une usine de copie, mais c'est une discussion complètement différente.

lubrifiants polygènes
la source
1
Oui, c'est l'une de mes parties préférées du livre.
polygenelubricants
1
Je n'aime pas dire que clone () est cassé. Je préfère dire que le clone était une décision de conception terrible et peut vous nuire beaucoup si vous ne l'utilisez pas correctement. De plus, vous pourriez ne jamais faire confiance aux méthodes clone () d'autres personnes. Alors on se retrouve pareil, on essaie de l'éviter, mais ce n'est pas cassé.
santiagobasulto
4
L'utilisation du cteur de copie ne vous oblige-t-elle pas à savoir quelle implémentation de Map vous copiez? Cela semble être une limitation inutile.
jon-hanson
"qu'il ne fournit la méthode clone () que" parce que les gens l'attendent "" - source?
Adam Parkin
60

Ni l'un ni l'autre: le constructeur auquel vous faites référence est défini pour l' implémentation HashMap d'un Map , (ainsi que pour d'autres) mais pas pour l'interface Map elle-même (par exemple, considérez l' implémentation Provider de l'interface Map: vous ne trouvera pas ce constructeur).

En revanche, il n'est pas conseillé d'utiliser la clone()méthode, comme l'explique Josh Bloch.

En ce qui concerne l'interface Map (et votre question, dans laquelle vous demandez comment copier une Map, pas un HashMap), vous devez utiliser Map # putAll () :

Copie tous les mappages de la carte spécifiée vers cette carte (opération facultative). L'effet de cet appel est équivalent à celui d'appeler put (k, v) sur cette mappe une fois pour chaque mappage de la clé k à la valeur v dans la mappe spécifiée.

Exemple:

// 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
la source
2
Alors pour clarifier: si vous savez que vous copiez vers une implémentation Mapdont a un constructeur de copie, il n'y a aucune raison de ne pas utiliser le constructeur de copie alors?
Adam Parkin
2
Exactement, et vous pouvez même le penser l'inverse: si vous utilisez, putAllvous n'avez pas besoin de savoir si l' Mapimplémentation que vous utilisez a un constructeur de copie ou non. Un simple constructeur de copie de toute Mapimplémentation est donc redondant.
Luca Fagioli
1
Bien sûr, bien que généralement j'aime mieux les 1-liners que les 2-liners. ;)
Adam Parkin
11

Copiez une carte sans connaître sa mise en œuvre:

static final Map shallowCopy(final Map source) throws Exception {
    final Map newMap = source.getClass().newInstance();
    newMap.putAll(source);
    return newMap;
}
Terris
la source
3
Pensez à ajouter <K,V>des paramètres de type pour garantir la sécurité du type.
Barett
1
Qu'en est-il des cartes sans constructeurs sans argument?
Isaac Saffold