Почему работает этот код, несмотря на стирание типов?

public class Solution<T extends HashMap> {
    private T map;

    public Solution(T map) {
        this.map = map;
    }

    public T getMap() {
        return map;
    }

    public static void main(String[] args) {
        HashMap<String, Integer> hashMap = new HashMap<>();
        hashMap.put("string", 4);
        Solution solution = new Solution(hashMap);
        HashMap mapFromSolution = solution.getMap();
        System.out.println(mapFromSolution.getClass());


        LinkedHashMap<Solution, Solution> hashMap2 = new LinkedHashMap<>();
        hashMap2.put(solution, solution);
        Solution<LinkedHashMap> solution2 = new Solution(hashMap2);
        LinkedHashMap mapFromSolution2 =  solution2.getMap();   // no need to cast
        System.out.println(mapFromSolution2.getClass());
    }
}

Почему solution2.getMap() возвращает LinkedHashMap без приведения типа? Разве из-за стирания типов метод не должен возвращать объекты HashMap?


Ответы (2 шт):

Автор решения: Byb

Почему solution2.getMap() возвращает LinkedHashMap без приведения типа?

Собственно, потому же, почему, например, метод get() для List<String> возвращает объекты типа String, для List<Car> - объекты типа Car и так далее.

Вот в этой строке

Solution<LinkedHashMap> solution2 = new Solution(hashMap2);

вы явным образом указываете, что при работе с solution2 типом T будет являться LinkedHashMap. Так что T, как и должно, отовсюду подменяется на LinkedHashMap, в том числе и в методе get().

И поэтому когда вы получаете геттером мапу, вам выдаётся в точности объект типа LinkedHashMap.

Можете попробовать даже сделать так:

String abc = solution2.getMap();

Вы увидите, что среда разработки справедливо ругается: мол метод возвращаемым типом имеет LinkedHashMap, а мы пытаемся его результат присвоить строке. Такой код не запустится. Ведь подстановка типов происходит ещё на этапе компиляции.

Обобщения для того и нужны: избавить программиста от излишнего приведения типов, и ошибки, связанные с неправильным использованием типов, перенести с этапа выполнения на этап компиляции.

→ Ссылка
Автор решения: Nowhere Man

Стирание типов происходит во время выполнения, а не при компиляции, поэтому компилятор ещё будет "знать", что при объявлении

Solution<LinkedHashMap> solution2 = new Solution(hashMap2);

переменная solution2 будет "типизирована" типом LinkedHashMap, несмотря на то, что в правой части будет создан "сырой" экземпляр Solution (о чём будет выдано соответствующее предупреждение компилятора).

Вот если бы solution2 сразу была объявлена как истинно-"сырая" (без указания обобщённого типа), тогда можно было получить желаемую ошибку времени компиляции.
Также, несмотря на "сырость", при создании экземпляра Solution всё равно потребуется использовать экземпляр HashMap или его подкласса.

Solution treeSolution = new Solution(new TreeMap()); // error: incompatible types: TreeMap cannot be converted to HashMap

// полностью сырое объявление, Т = HashMap
Solution solution2 = new Solution(hashMap2);
LinkedHashMap mapFromSolution2 =  solution2.getMap(); // error: incompatible types: HashMap cannot be converted to LinkedHashMap

и тогда потребовалось бы применить приведение типов для метода getMap:

Solution solution2 = new Solution(hashMap2);
LinkedHashMap mapFromSolution2 =  (LinkedHashMap) solution2.getMap(); // ok

Ещё один пример, когда объявление мапы типизировано, но создаётся "сырая" мапа и "сырая" ссылка-псевдоним на мапу:

HashMap<String, Integer> hashMap = new HashMap();

hashMap.put("string 3", 3);
// compile error: incompatible types: int cannot be converted to String
// hashMap.put(1, "string 1");

HashMap rawAlias = hashMap;

rawAlias.put(4, "string 4"); // типы ключа/значения не играют роли

// у сырого псевдонима всё хорошо
rawAlias.forEach((k, v) -> System.out.println(k + " -> " + v));

// у типизированного оригинала рантайм-проблема: ClassCastException
hashMap.forEach((k, v) -> System.out.println(k + " -> " + v));

Вывод:

4 -> string 4
string 3 -> 3
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
    at java.base/java.util.HashMap.forEach(HashMap.java:1421)
    at Main.main(Main.java:15)
→ Ссылка