В чем разница между java.util.Map.putAll(Map<>) и java.util.Map.put(Integer, Object) в цикле

В приведенном ниже коде я обнаружил, что использование метода putAll может вызвать проблемы, если мы передадим карту в параметре

public class Main {

    public static void main(String...strings ) {
        Etudiant e1=new  Etudiant(5, "A");
        Etudiant e2=new  Etudiant(6, "B");

        Map<Integer, Etudiant> map= new HashMap<>();
        map.put(1, e1);
        map.put(2, e2);

        Map<Integer, Etudiant> map2= new HashMap<>();
        map2.put(1,map.get(1));
        map2.put(1,map.get(2));

        changeMe(map2);
        System.out.println(map.get(1));

        Map<Integer, Etudiant> map3= new HashMap<>();
        map3.putAll(map);
        changeMe(map3);
        System.out.println(map.get(1));
    }

    private static void changeMe(Map<Integer, Etudiant> etudiants) {
        etudiants.get(1).name="K";
    }
}
}

Вот результат вывода:

Etudiant [age=5, name=A]
Etudiant [age=5, name=K]

Не могли бы вы объяснить разницу?

Почему после использования putAll объект меняется?

3 ответа

Решение

Ваш код подробно объяснен

Etudiant e1=new  Etudiant(5, "A");
Etudiant e2=new  Etudiant(6, "B");

Map<Integer, Etudiant> map= new HashMap<>();
map.put(1, e1);
map.put(2, e2);

map теперь содержит {1=Etudiant(5, "A"), 2=Etudiant(6, "B")}

Map<Integer, Etudiant> map2= new HashMap<>();
map2.put(1,map.get(1));
map2.put(1,map.get(2));

map2 теперь содержит {1=Etudiant(6, "B")}

changeMe(map2);
System.out.println(map.get(1));

Etudiant(6, "B") был переименован Etudiant(6, "K"), так:
map теперь содержит {1=Etudiant(5, "A"), 2=Etudiant(6, "K")}
map2 теперь содержит {1=Etudiant(6, "K")}
и это напечатано:

Этудиант (5, "А")

Map<Integer, Etudiant> map3= new HashMap<>();
map3.putAll(map);

map3 содержание является копией map содержание, так:
map3 теперь содержит {1=Etudiant(5, "A"), 2=Etudiant(6, "K")}

changeMe(map3);
System.out.println(map.get(1));

Etudiant(5, "A") был переименован Etudiant(5, "K"), так:
map теперь содержит {1=Etudiant(5, "K"), 2=Etudiant(6, "K")}
map2 теперь содержит {1=Etudiant(6, "K")}
map3 теперь содержит {1=Etudiant(5, "K"), 2=Etudiant(6, "K")}
и это напечатано:

Этудиант (5, "К")

Код работает точно так, как вы его запрограммировали.


Все вышеперечисленное можно легко увидеть, добавив несколько операторов печати, что является одним из способов отладки вашего кода.

public class Test {
    public static void main(String[] args) {
        Etudiant e1=new  Etudiant(5, "A");
        Etudiant e2=new  Etudiant(6, "B");

        Map<Integer, Etudiant> map= new HashMap<>();
        map.put(1, e1);
        map.put(2, e2);
        System.out.println("map:  " + map);

        Map<Integer, Etudiant> map2= new HashMap<>();
        map2.put(1,map.get(1));
        map2.put(1,map.get(2));
        System.out.println("map2: " + map2);

        changeMe(map2);
        System.out.println("map:  " + map);
        System.out.println("map2: " + map2);
        System.out.println(map.get(1));

        Map<Integer, Etudiant> map3= new HashMap<>();
        map3.putAll(map);
        System.out.println("map3: " + map3);

        changeMe(map3);
        System.out.println("map:  " + map);
        System.out.println("map2: " + map2);
        System.out.println("map3: " + map3);
        System.out.println(map.get(1));
    }
    private static void changeMe(Map<Integer, Etudiant> etudiants) {
        System.out.print("Renamed " + etudiants.get(1));
        etudiants.get(1).name="K";
        System.out.println(" to " + etudiants.get(1));
    }
}
class Etudiant {
    int id;
    String name;
    Etudiant(int id, String name) {
        this.id = id;
        this.name = name;
    }
    @Override
    public String toString() {
        return "Etudiant(" + this.id + ", \"" + this.name + "\")";
    }
}

Выход

map:  {1=Etudiant(5, "A"), 2=Etudiant(6, "B")}
map2: {1=Etudiant(6, "B")}
Renamed Etudiant(6, "B") to Etudiant(6, "K")
map:  {1=Etudiant(5, "A"), 2=Etudiant(6, "K")}
map2: {1=Etudiant(6, "K")}
Etudiant(5, "A")
map3: {1=Etudiant(5, "A"), 2=Etudiant(6, "K")}
Renamed Etudiant(5, "A") to Etudiant(5, "K")
map:  {1=Etudiant(5, "K"), 2=Etudiant(6, "K")}
map2: {1=Etudiant(6, "K")}
map3: {1=Etudiant(5, "K"), 2=Etudiant(6, "K")}
Etudiant(5, "K")

Потому что в map2:

Map<Integer, Etudiant> map2= new HashMap<>();
map2.put(1,map.get(1));
map2.put(1,map.get(2));

Вы перезаписываете первый элемент, поэтому карта выглядит так:

[1, e2]

Итак, когда вы звоните changeMe() меняется e2 не e1, поэтому, когда вы печатаете e1, оно останется неизменным. Затем, когда вы звоните putAll() это фактически изменит первый элемент, и изменение будет отражено.

Из документов для Map::putAll:

Эффект этого вызова эквивалентен эффекту вызова put(k, v) на этой карте один раз для каждого отображения от ключа k до значения v в указанной карте.

Таким образом, два эквивалентны


Измените свой код на:

Map<Integer, Etudiant> map2= new HashMap<>();
map2.put(1,map.get(1));
map2.put(2,map.get(2));

И вы должны получить ожидаемые результаты

И то и другое putAll и серия put достигнет того же результата. Но в зависимости от реализации карты, putAll иногда может быть быстрее. Например, если записи на карту должны получить блокировку, то putAll может получить замок один раз и использовать его для всех замков. Или, если карта должна выполнять какое-то внутреннее обслуживание или учет между записями, она также может оптимизировать их.

Это также хорошая строка, если у вас уже есть коллекция, так что она менее многословна, чем цикл.

Другие вопросы по тегам