Płytka kopia mapy w języku Java

107

Jak rozumiem, istnieje kilka sposobów (być może także innych) na utworzenie płytkiej kopii pliku Mapw Javie:

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();

Czy jeden sposób jest preferowany od drugiego, a jeśli tak, to dlaczego?

Warto wspomnieć o tym, że druga metoda daje ostrzeżenie o „Unchecked Cast”. Musisz więc dodać, @SuppressWarnings("unchecked")aby to obejść, co jest trochę irytujące (patrz poniżej).

@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
źródło
W nowszych wersjach Javy (dokładnie od Javy 10) można użyć statycznej metody Map.copyOf . Ale pamiętaj, że zwraca niemodyfikowalną mapę!
Oleksandr Pyrohov

Odpowiedzi:

106

Zawsze lepiej jest kopiować za pomocą konstruktora kopiującego. clone()w Javie jest uszkodzony (patrz SO: Jak poprawnie zastąpić metodę clone? ).

Josh Bloch on Design - Copy Constructor versus Cloning

Jeśli przeczytałeś artykuł o klonowaniu w mojej książce, zwłaszcza jeśli czytasz między wierszami, będziesz wiedział, że moim zdaniem clonejest głęboko zepsuty. […] Szkoda, że Cloneablejest zepsuty, ale się zdarza.

Bloch (który, nawiasem mówiąc, zaprojektował i wdrożył framework Collection) poszedł nawet dalej, mówiąc, że dostarcza clone()metody tylko „dlatego, że ludzie tego oczekują”. W rzeczywistości wcale NIE zaleca jego używania.


Myślę, że bardziej interesującą debatą jest to, czy konstruktor kopii jest lepszy niż fabryka kopii, ale to zupełnie inna dyskusja.

smary wielogenowe
źródło
1
Tak, to jedna z moich ulubionych części książki.
smary wielogenowe
1
Nie lubię mówić, że clone () jest uszkodzony. Wolę powiedzieć, że klon był okropną decyzją projektową i może ci bardzo zaszkodzić, jeśli nie użyjesz go prawidłowo. Ponadto możesz nigdy nie ufać metodom clone () innych osób. Więc kończymy podobnie, staramy się tego unikać, ale nie jest zepsuty.
santiagobasulto
4
Czy użycie edytora kopiowania nie wymaga, abyś wiedział, którą implementację mapy kopiujesz? Wydaje się, że jest to niepotrzebne ograniczenie.
jon-hanson
"że udostępnia metodę clone () tylko" ponieważ ludzie tego oczekują "" - źródło?
Adam Parkin,
60

Żaden z tych dwóch: konstruktor , do którego się odnosisz, nie jest zdefiniowany dla implementacji HashMap w Map (jak również dla innych), ale nie dla samego interfejsu Map (na przykład rozważ implementację dostawcy interfejsu Map: ty nie znajdzie tego konstruktora).

Z drugiej strony nie zaleca się stosowania tej clone()metody, jak wyjaśnia Josh Bloch.

W odniesieniu do interfejsu mapy (i twojego pytania, w którym pytasz, jak skopiować mapę, a nie HashMap), powinieneś użyć Map # putAll () :

Kopiuje wszystkie mapowania z określonej mapy do tej mapy (operacja opcjonalna). Efekt tego wywołania jest równoważny wywołaniu metody put (k, v) na tej mapie raz dla każdego odwzorowania klucza k na wartość v w określonej mapie.

Przykład:

// 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
źródło
2
A więc wyjaśnienie: jeśli wiesz , że kopiujesz do implementacji, Mapktóra ma konstruktor kopiujący, nie ma powodu, aby nie używać konstruktora kopiującego?
Adam Parkin
2
Dokładnie, możesz nawet pomyśleć na odwrót: jeśli używasz putAll, nie musisz wiedzieć, czy Mapimplementacja, której używasz, ma konstruktor kopiujący, czy nie. Tak więc zwykły konstruktor kopiujący dowolnej Mapimplementacji jest zbędny.
Luca Fagioli
1
Jasne, chociaż generalnie wolę 1-wkładki niż 2-wkładki. ;)
Adam Parkin
11

Skopiuj mapę bez znajomości jej implementacji:

static final Map shallowCopy(final Map source) throws Exception {
    final Map newMap = source.getClass().newInstance();
    newMap.putAll(source);
    return newMap;
}
Terris
źródło
3
Rozważ dodanie <K,V>parametrów typu, aby zapewnić bezpieczeństwo typu.
Barett
1
A co z mapami bez konstruktorów bezargumentowych?
Isaac Saffold