Kopieren einer HashMap in Java

116

Ich versuche, einen temporären Container einer Klasse zu behalten, die Mitglied enthält:

HashMap<Integer,myObject> myobjectHashMap

Eine Klasse namens myobjectsList

Dann mach ich

myojbectsListA = new myojbectsList();
myojbectsListB = new myobjectsList();

dann: Füge einige Hashmap-Elemente zu A hinzu (like2)

dann

myobjectListB = myobjectListA; //B has 2

dann: Fügen Sie Hash-Map-Elemente zu A hinzu; (wie 4 weitere)

Geben Sie dann A zu den in B gespeicherten Elementen zurück.

myobjectListA = myobjectListb;

Aber wenn ich das mache, wächst B mit A, während ich A Hashmap-Elemente hinzufüge. A enthält jetzt 6 Elemente, weil B 6 hatte.

Ich möchte, dass A Original 2 nach der letzten Bewertung in C ++ noch am Ende hat. Ich würde Kopie mit Objekten verwenden. Was ist das Java-Äquivalent?

Hinzugefügt: OK Ich habe etwas ausgelassen, um dies zu erklären. MyObjectsList enthält keine HashMap, sondern leitet sich von einer Klasse MyBaseOjbectsList ab, die das HashMap-Mitglied hat, und MyObjectsList erweitert MyBaseOjbectsList. Macht das einen Unterschied?

user691305
quelle
1
Können Sie eine SSCCE veröffentlichen , um besser zu verstehen, was Sie bisher getan haben?
Assylias
2
Ihre Objekte sollten die klonbare Schnittstelle implementieren, andernfalls teilen Zuweisungen wie MyObjectB = MyObjectA der JVM einfach mit, dass beide Variablen auf denselben Speicherort im Speicher verweisen. Nicht zwei verschiedene Objekte.
Mike McMahon
Übrigens ist die überwältigende Redewendung (praktisch ein Gesetz), Klassennamen groß zu schreiben. Dadurch werden Ihre Beispiele für diejenigen von uns besser lesbar, die diese Dinge als schnelle Hinweise beim Scannen von Beispielcode verwenden.
Kevin Welker
1
um @KevinWelker hinzuzufügen, hilft es auch dem Syntax-Textmarker, die Klassennamen hervorzuheben
Ratschenfreak
@ Mike - Danke, aber versucht, klonbar zu machen, es hat sie beide wachsen lassen, wenn man gewachsen ist. Das will ich nicht
user691305

Antworten:

226

Wenn Sie eine Kopie der HashMap möchten, müssen Sie eine neue mit erstellen.

myobjectListB = new HashMap<Integer,myObject>(myobjectListA);

Dadurch wird eine (flache) Kopie der Karte erstellt.

Ratschenfreak
quelle
4
oder wenn es bereits erstellt wurde, verwenden SiemyObjectListB.addAll(myObjectListA)
Kevin Welker
Vielleicht verstehe ich das nicht, aber ich brauche keine Kopie der Karte. Ich brauche eine Kopie der Klasse, die die Karte enthält. ? Dort muss für myObjectListB eine von MyojbectsList abgeleitete Klasse keine Hashmap sein.
user691305
8
@ Kevin Welker, addAllist für HashSet. putAllist für HashMap.
Andrea Motto
2
Es hat nicht funktioniert. In meinem Fall zeigte die "neue" Variable immer noch auf dasselbe Objekt.
Thomio
4
@Thomio Ich erwähnte das, das ist, was "flache Kopie" bedeutet. Wenn Sie eine tiefe Kopie benötigen, müssen Sie die Karte durchlaufen und kopieren, was Sie brauchen.
Ratschenfreak
15

Sie können auch verwenden

clone()

Methode zum Kopieren aller Elemente von einer Hashmap in eine andere Hashmap

Programm zum Kopieren aller Elemente von einer Hashmap in eine andere

import java.util.HashMap;

public class CloneHashMap {    
     public static void main(String a[]) {    
        HashMap hashMap = new HashMap();    
        HashMap hashMap1 = new HashMap();    
        hashMap.put(1, "One");
        hashMap.put(2, "Two");
        hashMap.put(3, "Three");
        System.out.println("Original HashMap : " + hashMap);
        hashMap1 = (HashMap) hashMap.clone();
        System.out.println("Copied HashMap : " + hashMap1);    
    }    
}

Quelle: http://www.tutorialdata.com/examples/java/collection-framework/hashmap/copy-all-elements-from-one-hashmap-to-another

user2236292
quelle
14
Sie können, aber Sie sollten es wahrscheinlich nicht, weil Cloneable laut Joshua Blochs Effective Java # 11 im Wesentlichen kaputt ist.
Visionary Software Solutions
13
Beachten Sie außerdem, dass clone () eine flache Kopie zurückgibt.
Vinay W
3
Was bedeutet flach überhaupt? Wenn die Karte nicht exakt kopiert wird (mit intakten Schlüsseln / Werten), warum dann? Könnte auch eine neue Karte erstellen, oder?
Azurespot
2
@ Azurespot flach bedeutet, dass keine verschachtelten Objekte und ihre verschachtelten Objekte
kopiert
2
Wenn Sie sich die Quelle ansehen, erstellt clone () lediglich eine neue HashMapund putAllalles darin. Es ist völlig sinnlos.
Ariel
12

Der Unterschied besteht darin, dass sich Ihr Objekt in C ++ auf dem Stapel befindet, während sich Ihr Objekt in Java auf dem Heap befindet. Wenn A und B Objekte sind, können Sie in Java jederzeit Folgendes tun:

B = A

A und B zeigen auf dasselbe Objekt, also tun Sie alles, was Sie A antun, B und umgekehrt.

Verwenden Sie new, HashMap()wenn Sie zwei verschiedene Objekte möchten.

Und Sie können Map.putAll(...)damit Daten zwischen zwei Karten kopieren.

ControlAltDel
quelle
Ist das dasselbe wie zu sagen, dass Java nur Referenz ist und C ++ manchmal Referenz und manchmal Wert ist? Aber ich möchte keine HashMap, ich möchte keine Kopie der Klasse, die ich als temporären Speicher verwenden kann.
user691305
Ich habe keinen Zugriff auf die HashMap in der Basisklasse. Auch hier muss ich mit Klassen arbeiten, die von der Basis abgeleitet sind. Ich kann nicht auf die darin enthaltene HashMap zugreifen.
user691305
6

Hier gibt es eine kleine (RIESIGE) Untertreibung. Wenn Sie ein Objekt HashMapmit verschachtelten Strukturen HashMap.putAll()kopieren möchten , wird es als Referenz kopiert, da es nicht genau weiß, wie Ihr Objekt kopiert werden soll. Beispielsweise:

import java.util.*;
class Playground {
    public static void main(String[ ] args) {
        Map<Integer, Map<Integer,List<Float>>> dataA = new HashMap<>();
        Map<Integer, Map<Integer,List<Float>>> dataB = new HashMap<>();

        dataA.put(1, new HashMap<>());
        dataB.putAll(dataA);

        assert(dataB.get(1).size() == 0);

        dataA.get(1).put(2, new ArrayList<>());

        if (dataB.get(1).size() == 1) { // true
            System.out.println(
                "Sorry object reference was copied - not the values");
        }
    }
}

Grundsätzlich müssen Sie die Felder wie hier selbst kopieren

List <Float> aX = new ArrayList<>(accelerometerReadingsX);
List <Float> aY = new ArrayList<>(accelerometerReadingsY);

List <Float> gX = new ArrayList<>(gyroscopeReadingsX);
List <Float> gY = new ArrayList<>(gyroscopeReadingsY);

Map<Integer, Map<Integer, Float>> readings = new HashMap<>();

Map<Integer,List<Float>> accelerometerReadings = new HashMap<>();
accelerometerReadings.put(X_axis, aX);
accelerometerReadings.put(Y_axis, aY);
readings.put(Sensor.TYPE_ACCELEROMETER, accelerometerReadings);

Map<Integer,List<Float>> gyroscopeReadings = new HashMap<>();
gyroscopeReadings.put(X_axis, gX);
gyroscopeReadings.put(Y_axis, gY);
readings.put(Sensor.TYPE_GYROSCOPE, gyroscopeReadings);
verletzen
quelle
4

Wenn Sie in Java schreiben:

Object objectA = new Object();
Object objectB = objectA;

objectAund objectBsind gleich und verweisen auf die gleiche Referenz. Wenn Sie eines ändern, wird das andere geändert. Wenn Sie also den Status von objectA(nicht dessen Referenz) objectBändern, wird diese Änderung ebenfalls berücksichtigt.

Wenn Sie jedoch schreiben:

objectA = new Object()

Zeigt dann objectBimmer noch auf das erste Objekt, das Sie erstellt haben (Original objectA), während objectAes jetzt auf ein neues Objekt zeigt.

Assylien
quelle
das erklärt das Problem, was ist die Lösung? Oder ist dies eine Einschränkung von Java ohne Umgehung?
user691305
1
Das ist keine Einschränkung, es ist die Art und Weise, wie es funktioniert. Wenn Sie zwei verschiedene Objekte möchten, erstellen Sie zwei Objekte. Wenn Sie nur ein Objekt erstellen, sind alle Variablen, die auf dieses Objekt verweisen, gleich.
Assylias
Ich schlage vor, Sie erstellen eine neue Frage mit dem gesamten relevanten Code (idealerweise eine SSCCE ) und erklären genau, was Sie erreichen möchten .
Assylias
Das Erstellen eines neuen Objekts und das Löschen der ursprünglichen Werte ist nicht das Ziel. Ziel ist es, die ursprünglichen Werte beizubehalten und später in einer Kopie, die mehr neue Werte erhalten hat, erst wieder zu verwenden.
user691305
Nun, es kommt wirklich auf das Objekt an. Wenn es keine Möglichkeit zum Erstellen eines neuen Kopierobjekts bietet, ist dies kompliziert, da das Objekt nicht zum Kopieren konzipiert wurde (möglicherweise absichtlich - Sie können dies jedoch weiterhin mithilfe von Reflection tun). Wenn das Objekt diese Funktion implementiert (siehe zum Beispiel die Antwort mit dem Kopierkonstruktor von HashMap), verwenden Sie einfach die bereitgestellte Methode.
Assylias
3

Wenn wir ein Objekt in Java kopieren möchten, müssen wir zwei Möglichkeiten berücksichtigen: eine flache Kopie und eine tiefe Kopie .

Die flache Kopie ist der Ansatz, wenn wir nur Feldwerte kopieren. Daher kann die Kopie vom Originalobjekt abhängig sein. Beim Deep Copy- Ansatz stellen wir sicher, dass alle Objekte im Baum tief kopiert werden, sodass die Kopie nicht von einem früheren vorhandenen Objekt abhängig ist, das sich jemals ändern könnte.

Diese Frage ist die perfekte Definition für die Anwendung des Deep-Copy- Ansatzes.

Wenn Sie eine einfache HashMap<Integer, List<T>>Karte haben, erstellen wir zunächst eine solche Problemumgehung. Erstellen einer neuen Instanz des List<T>.

public static <T> HashMap<Integer, List<T>> deepCopyWorkAround(HashMap<Integer, List<T>> original)
{
    HashMap<Integer, List<T>> copy = new HashMap<>();
    for (Map.Entry<Integer, List<T>> entry : original.entrySet()) {
        copy.put(entry.getKey(), new ArrayList<>(entry.getValue()));
    }
    return copy;
}

Dieser verwendet eine Stream.collect()Methode zum Erstellen der Klonzuordnung, verwendet jedoch dieselbe Idee wie die vorherige Methode.

public static <T> Map<Integer, List<T>> deepCopyStreamWorkAround(Map<Integer, List<T>> original)
{
    return original
            .entrySet()
            .stream()
            .collect(Collectors.toMap(Map.Entry::getKey, valueMapper -> new ArrayList<>(valueMapper.getValue())));
}   

Wenn die darin enthaltenen Instanzen Tjedoch auch veränderbare Objekte sind , haben wir ein großes Problem. In diesem Fall ist eine wirklich tiefe Kopie eine Alternative, die dieses Problem löst. Sein Vorteil ist, dass mindestens jedes veränderbare Objekt im Objektgraphen rekursiv kopiert wird. Da die Kopie nicht von einem zuvor erstellten veränderlichen Objekt abhängig ist , wird sie nicht versehentlich geändert, wie wir es bei der flachen Kopie gesehen haben.

Um das zu lösen, werden diese Deep Copy-Implementierungen die Arbeit erledigen.

public class DeepClone
{
    public static void main(String[] args)
    {
        Map<Long, Item> itemMap = Stream.of(
                entry(0L, new Item(2558584)),
                entry(1L, new Item(254243232)),
                entry(2L, new Item(986786)),
                entry(3L, new Item(672542)),
                entry(4L, new Item(4846)),
                entry(5L, new Item(76867467)),
                entry(6L, new Item(986786)),
                entry(7L, new Item(7969768)),
                entry(8L, new Item(68868486)),
                entry(9L, new Item(923)),
                entry(10L, new Item(986786)),
                entry(11L, new Item(549768)),
                entry(12L, new Item(796168)),
                entry(13L, new Item(868421)),
                entry(14L, new Item(923)),
                entry(15L, new Item(986786)),
                entry(16L, new Item(549768)),
                entry(17L, new Item(4846)),
                entry(18L, new Item(4846)),
                entry(19L, new Item(76867467)),
                entry(20L, new Item(986786)),
                entry(21L, new Item(7969768)),
                entry(22L, new Item(923)),
                entry(23L, new Item(4846)),
                entry(24L, new Item(986786)),
                entry(25L, new Item(549768))
        ).collect(entriesToMap());


        Map<Long, Item> clone = DeepClone.deepClone(itemMap);
        clone.remove(1L);
        clone.remove(2L);

        System.out.println(itemMap);
        System.out.println(clone);
    }

    private DeepClone() {}

    public static <T> T deepClone(final T input)
    {
        if (input == null) return null;

        if (input instanceof Map<?, ?>) {
            return (T) deepCloneMap((Map<?, ?>) input);
        } else if (input instanceof Collection<?>) {
            return (T) deepCloneCollection((Collection<?>) input);
        } else if (input instanceof Object[]) {
            return (T) deepCloneObjectArray((Object[]) input);
        } else if (input.getClass().isArray()) {
            return (T) clonePrimitiveArray((Object) input);
        }

        return input;
    }

    private static Object clonePrimitiveArray(final Object input)
    {
        final int length = Array.getLength(input);
        final Object output = Array.newInstance(input.getClass().getComponentType(), length);
        System.arraycopy(input, 0, output, 0, length);
        return output;
    }

    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;
        if (input instanceof LinkedList<?>) {
            clone = new LinkedList<>();
        } else if (input instanceof SortedSet<?>) {
            clone = new TreeSet<>();
        } else if (input instanceof Set) {
            clone = new HashSet<>();
        } else {
            clone = new ArrayList<>();
        }

        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;
        if (map instanceof LinkedHashMap<?, ?>) {
            clone = new LinkedHashMap<>();
        } else if (map instanceof TreeMap<?, ?>) {
            clone = new TreeMap<>();
        } else {
            clone = new HashMap<>();
        }

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

        return clone;
    }
}
Teocci
quelle
2

Seit Java 10 ist es möglich zu verwenden

Map.copyOf

zum Erstellen einer flachen Kopie , die ebenfalls unveränderlich ist. (Hier ist seine Javadoc ). Wie in dieser Antwort erwähnt , benötigen Sie für eine tiefe Kopie eine Art Wertzuordnung, um eine sichere Kopie der Werte zu erstellen. Sie müssen jedoch keine Schlüssel kopieren , da diese unveränderlich sein müssen .

xuesheng
quelle
1

Da diese Frage immer noch unbeantwortet ist und ich ein ähnliches Problem hatte, werde ich versuchen, dies zu beantworten. Das Problem (wie bereits erwähnt) besteht darin, dass Sie nur Verweise auf dasselbe Objekt kopieren und daher durch eine Änderung an der Kopie auch das Ursprungsobjekt geändert wird. Sie müssen also das Objekt (Ihren Kartenwert) selbst kopieren. Der einfachste Weg, dies zu tun, besteht darin, alle Ihre Objekte so zu gestalten, dass sie die serialisierbare Schnittstelle implementieren. Serialisieren und deserialisieren Sie dann Ihre Karte, um eine echte Kopie zu erhalten. Sie können dies selbst tun oder die Apache Commons SerializationUtils # clone () verwenden, die Sie hier finden: https://commons.apache.org/proper/commons-lang/javadocs/api-2.6/org/apache/commons/ lang / SerializationUtils.html Beachten Sie jedoch, dass dies der einfachste Ansatz ist, es jedoch eine teure Aufgabe ist, viele Objekte zu serialisieren und zu deserialisieren.

KIC
quelle
0

Wenn Sie ein Objekt einem anderen zuweisen, kopieren Sie lediglich den Verweis auf das Objekt und nicht den Inhalt. Sie müssen lediglich Ihr Objekt B nehmen und den Inhalt von Objekt A manuell kopieren.

Wenn Sie dies häufig tun, können Sie eine clone()Methode für die Klasse implementieren , mit der ein neues Objekt desselben Typs erstellt und der gesamte Inhalt in das neue Objekt kopiert wird.

Zickdon
quelle
Clone tat dasselbe, es schien nur die Referenz zu bestehen.
user691305
Auch wenn man seine HashMap-Anzahl erhöht, tut dies auch die Kopie, will das nicht. Ich möchte ideenabhängige Container (in diesem Fall Klassen) des gleichen Typs.
user691305
0

Da das OP erwähnt hat, dass er keinen Zugriff auf die Basisklasse hat, in der sich eine HashMap befindet - ich fürchte, es sind nur sehr wenige Optionen verfügbar.

Eine (schmerzhaft langsame und ressourcenintensive) Möglichkeit, eine tiefe Kopie eines Objekts in Java auszuführen, besteht darin, die "serialisierbare" Schnittstelle zu missbrauchen, die viele Klassen absichtlich oder unbeabsichtigt erweitern, und diese dann zu verwenden, um Ihre Klasse in ByteStream zu serialisieren. Nach der De-Serialisierung erhalten Sie eine tiefe Kopie des betreffenden Objekts.

Eine Anleitung dazu finden Sie hier: https://www.avajava.com/tutorials/lessons/how-do-i-perform-a-deep-clone-using-serializable.html

Felteh
quelle