Java 泛型类型转换

Java 泛型类型转换,java,generics,casting,Java,Generics,Casting,我有以下课程(简化但仍然是一个工作示例): 为什么只有在变量赋值时才会抛出异常?如果(T)强制转换不起作用,我如何强制执行类型一致性 add方法不应该抛出一个ClassCastException 不,不应该(尽管我希望是这样)。长话短说,泛型的Java实现在编译代码后会丢弃类型信息,因此List可以接受任何对象,并且add方法中的强制转换不会被检查 为什么只有在变量赋值时才会抛出异常 因为编译器插入了对双精度的转换。Java编译器知道get的返回类型是T,它是Double,因此它插入了一个强制转

我有以下课程(简化但仍然是一个工作示例):

为什么只有在变量赋值时才会抛出异常?如果
(T)
强制转换不起作用,我如何强制执行类型一致性

add方法不应该抛出一个
ClassCastException

不,不应该(尽管我希望是这样)。长话短说,泛型的Java实现在编译代码后会丢弃类型信息,因此
List
可以接受任何
对象
,并且
add
方法中的强制转换不会被检查

为什么只有在变量赋值时才会抛出异常

因为编译器插入了对双精度的转换。Java编译器知道
get
的返回类型是
T
,它是
Double
,因此它插入了一个强制转换,以匹配分配结果的变量
d
的类型

以下是如何实现通用安全强制转换:

class Test<T> {
    private final Class<T> cl;
    List<T> l = new ArrayList<>();

    public Test(Class<T> c) {
        cl = c;
    }

    public void add(Object o) {
        l.add(cl.cast(o));
    }
}
类测试{
私人期末班;
列表l=新的ArrayList();
公开考试(c级){
cl=c;
}
公共无效添加(对象o){
l、 添加(第(o)项);
}
}

现在,强制转换是由一个
对象执行的,因此如果试图插入一个类型不正确的对象,您将得到一个
ClassCastException

作为替代解决方案,您可以使用:


为了确保此资源的完整性,以下是转换到泛型之间编译字节码的区别:

public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: invokeinterface #7,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    10: pop
    11: return
并显式转换为不带泛型的
Double

public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: checkcast     #7                  // class java/lang/Double
     8: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    13: pop
    14: return
您可以看到,带有泛型的版本根本不执行
checkcast
指令(多亏了,所以在为其提供不匹配类的数据时,不应该期望出现异常。不幸的是,这并没有得到更严格的实施,但这是有意义的,因为泛型用于进行更严格的编译时类型检查,并且由于类型擦除,在运行时没有太大帮助

Java将检查函数参数的类型,以查看是否存在类型匹配,或者是否可以执行类型升级。在您的情况下,
String
是参数的类型,并且可以升级为
对象
,这是确保函数调用工作的编译时类型检查的范围

有几个选项,dasblinkenlight的解决方案可能是最优雅的(例如,如果您正在重写继承的
add
方法,或者计划传递
add
方法等,则可能无法更改方法签名)

另一个可能有用的选项是使用有界类型参数而不是无界类型参数。由于类型擦除,编译后无界类型参数将完全丢失,但使用有界类型参数将用它必须扩展的实例替换泛型类型的实例

class Test<T extends Number> {

当尝试添加字符串时,此类定义会生成所需的
ClassCastException

在编译时,您是否碰巧收到任何关于“未检查强制转换”的警告?(您应该这样做了…)
public void add(to)有什么不好的地方;
?回答问题的另一部分:由于类型擦除,
ArrayList
无法检查并知道您在列表中输入的内容是否与您设置的类型参数兼容。
class Test<T> {
    List<T> l;

    public Test(Class<T> c) {
        l = Collections.checkedList(new ArrayList<T>(), c);
    }

    public void add(Object o) {
        l.add((T) o);
    }
}
Exception in thread "main" java.lang.ClassCastException: Attempt to insert 
  class java.lang.Integer element into collection with element type class java.lang.Double
    at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
    at java.util.Collections$CheckedCollection.add(Collections.java:3080)
    at Test.add(Test.java:13)
public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: invokeinterface #7,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    10: pop
    11: return
public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: checkcast     #7                  // class java/lang/Double
     8: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    13: pop
    14: return
class Test<T extends Number> {
public void add(java.lang.Object);
  Code:
     0: aload_0
     1: getfield      #4                  // Field l:Ljava/util/List;
     4: aload_1
     5: checkcast     #7                  // class java/lang/Number
     8: invokeinterface #8,  2            // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z
    13: pop
    14: return