为什么<T extend Map>可以分配给列表

问题描述 投票:0回答:1

我错过了 Java 泛型类型的问题

public class MyTest {
    @Test
    public void test1() throws Exception {
        List<Integer> list = getMapInstance();
    }

    public static <T extends Map<Object, Object>> T getMapInstance() {
        T t = (T) new HashMap<>();
        return t;
    }
}

在代码中,我将一个T(扩展了Map)分配给一个List,我认为它会被编译时类型检查拦截。但它成功地通过了,当我运行它时,它抛出了 ClassCastException

//the bytecode of test1  
0 invokestatic #10 <org/example/BugTest.getMapInstance : ()Ljava/util/Map;>
3 checkcast #11 <java/util/List>
6 astore_1
7 return

我的JDK版本是11和17

我想知道为什么这段代码编译成功,请

我尝试创建一个实现List和Map接口的类,但是List和Map的remove方法会发生冲突

boolean remove(Object o);
V remove(Object key);
java generics classcastexception
1个回答
0
投票

此代码编译成功的原因是类型擦除,这是 Java 泛型的一个功能。类型擦除是在Java代码编译过程中删除与类型参数相关的所有信息的过程。因此,为 getMapInstance() 方法生成的字节码将如下所示:

public static Map getMapInstance() {
    Map t = new HashMap();
    return (Map) t;
}

由于类型信息在编译过程中被擦除,Java编译器无法检测到返回的对象实际上不是List类型。相反,它是 Map 类型,由于类型不匹配,可以将其分配给 List 变量。

字节码中的checkcast指令用于确保分配给列表变量的对象确实是List的实例。但是,由于类型信息被删除,因此此检查不会捕获类型不匹配。

当代码尝试将列表变量用作列表时,会在运行时抛出 ClassCastException。此时,Java 虚拟机 (JVM) 检测到类型不匹配并抛出异常。

总而言之,代码之所以能够成功编译,是因为类型擦除,即在编译过程中删除了类型信息。字节码中的校验指令不会捕获类型不匹配,因为类型信息被擦除。当代码尝试将列表变量用作列表时,会在运行时引发 ClassCastException。

需要注意的是,使用原始类型(即不指定类型参数)可能会导致类型不匹配和其他问题。通常建议使用参数化类型并尽可能避免使用原始类型。

© www.soinside.com 2019 - 2024. All rights reserved.