java hashmap позволяет использовать 2 дубликата ключа - PullRequest
2 голосов
/ 13 февраля 2020

Выполнение следующего кода:

public class Main {

    public static void main(String[] args) {
        Util util = new Util();
        util.addBlock(1);
        util.addBlocks(util.getBlocks());
    }

}

public class Util {

    public static HashMap<Long, Integer> blockMap = new HashMap<>();
    private Gson gson = new Gson();

    public void addBlocks(String json){
        Map map = gson.fromJson(json, Map.class);
        blockMap.putAll(map);
        System.out.println(blockMap.keySet());
    }

    public void addBlock(int i){
        blockMap.put(0L, i);
    }

    public String getBlocks(){
        return gson.toJson(blockMap);
    }
}

Я получаю вывод

[0, 0]

из печати System.out.println(blockMap.keySet());.

Итак, по какой-то причине я Map<Long, Integer> содержит два Long s со значением 0 в качестве ключа. И набор ключей Set<Long> с двумя 0 с. Но карта и набор не позволяют дублировать ключи, как это возможно?


Код сначала добавляет простую запись на карту:

blockMap.put(0L, i);

Затем я добавляю еще одну запись преобразовав карту в строку JSON, используя GSON и затем вернувшись к карте:

gson.toJson(blockMap);
...
Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

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

Ответы [ 3 ]

3 голосов
/ 13 февраля 2020

Объяснение

Вы интерпретируете свои результаты немного неправильно. Вы говорите, что он печатает

[0, 0]

и заключаете, что у вас есть два ключа типа Long со значением 0. Но это на самом деле неправильно. Один действительно Long, идущий отсюда:

blockMap.put(0L, i);

Но другой - String "0", который в печати выглядит так же.


Raw-types

Вы можете быть смущены, почему в вашем HashMap<Long, Block> есть строковый ключ. В конце концов, он определяет ключ как Long, а не как String.

Проблема здесь:

Map map = gson.fromJson(json, Map.class);
blockMap.putAll(map);

Здесь происходит то, что вы используете putAll, но предоставить Map. Обратите внимание, что у него нет генериков, это необработанный тип .

Используя необработанный тип, вы в основном жертвуете всеми типами безопасности и обещаниями Java "Да, да, поверь мне, я знаю, что делаю. Так что Java будет просто доверять вам, что вы на самом деле даете ему Map<Long, Block>, но вы на самом деле даете ему Map<String, String>.


Загрязнение кучи

В Java , генерики стираются во время выполнения . Это означает, что после прохождения компиляции Java больше ничего не знает о дженериках и, в основном, позволяет вам смешивать типы так, как вы хотите, это просто "Map<Object, Object>" более или менее.

Так что вам удалось обмануть Javas safe generi c -type-system и добавили String в карту, которая принимает только Long s в качестве ключей. Это называется куча загрязнений . И это главная причина, почему вы никогда не должны использовать raw-типы. Использование raw-типов бесполезно.

Небольшой пример загрязнения кучи:

List<Dog> dogs = new ArrayList<>(); // safe collection
dogs.add(new Dog());

// dogs.add(new Cat()); // not allowed, dogs is safe

List raw = dogs; // raw handle to safe collection
raw.add(new Cat()); // works! heap pollution

// compiles, but throws at run-time,
// since it is actually a cat, not a dog
Dog dog = dogs.get(1);

Примечания

Подробнее об этой теме читайте здесь c:

2 голосов
/ 13 февраля 2020

Надеюсь, следующий код очистит ваши сомнения:

public void addBlocks(String json) {
    Map map = gson.fromJson(json, Map.class);
    blockMap.putAll(map);
    System.out.println(blockMap.keySet());
    Iterator itr=blockMap.keySet().iterator();
    while(itr.hasNext()){
        System.out.println(itr.next().getClass());
    }
}

Вывод с этим кодом:

[0, 0]
class java.lang.Long
class java.lang.String
2 голосов
/ 13 февраля 2020

Проблема здесь в том, что @Zabuza указал, что вы используете необработанную Карту без шаблонов, которые стираются во время выполнения. Просто попробуйте этот пример:

for (Object key: Util.blockMap.keySet()) {
    System.out.println("key: " + key + " type: " + key.getClass());
}

, и вы увидите, что ваш первый ключ имеет тип Long, а второй ключ имеет тип String

Поскольку метод equals определяется внутри String как:

if (anObject instanceof String) {
    // ...
}
return false;

равно всегда будет возвращать false

...