通用类型转换
我有以下类(简化但仍然是一个工作示例):
class Test { List l = new ArrayList(); public Test() { } public void add(Object o) { l.add((T)o); } }
和测试代码:
Test t = new Test(); t.add(1); t.add(1.2); t.add(-5.6e-2); t.add("hello");
一切都很好,这不是我所期待的。 add
方法不应该抛出ClassCastException
吗? 如果我添加一个或多或少相同的get
方法:
public T get(int i) { return l.get(i); } .../... t.get(1); // OK. t.get(3); // OK (?) Double d = t.get(3); // throws ClassCastException
为什么仅在变量赋值时抛出exception? 如果(T)
强制转换不起作用,我该如何强制执行类型一致性?
add方法不应该抛出
ClassCastException
吗?
不,它不应该(虽然我希望它这样做)。 简而言之,Java实现generics在编译代码后会丢弃类型信息,因此List
允许使用任何Object
,并且不会检查add
方法中的强制转换。
为什么仅在变量赋值时抛出exception?
因为强制转换为Double
,所以编译器会插入。 Java编译器知道get
的返回类型是T
,它是Double
,因此它插入一个强制转换以匹配变量d
的类型,并将结果分配给该变量。
以下是如何实现通用安全转换:
class Test { private final Class cl; List l = new ArrayList<>(); public Test(Class c) { cl = c; } public void add(Object o) { l.add(cl.cast(o)); } }
现在,转换由Class
对象执行,因此在尝试插入不正确类型的对象时将获得ClassCastException
。
作为替代解决方案,您可以使用Collections.checkedList
:
class Test { List l; public Test(Class c) { l = Collections.checkedList(new ArrayList (), c); } public void add(Object o) { l.add((T) o); } }
这样您将获得以下exception:
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
并且没有generics的显式转换为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
您可以看到带有generics的版本根本不执行checkcast
指令(感谢类型擦除 ,因此在为数据提供不匹配的类时不应该期待exception。不幸的是,这不是更严格的强制执行但这是有道理的,因为generics用于进行更严格的编译时类型检查,并且由于类型擦除而在运行时没有太大帮助。
Java将检查函数参数的类型,以查看是否存在类型匹配,或者是否可以执行类型提升。 在您的情况下, String
是参数的类型,可以提升为Object
,这是确保函数调用有效的编译时类型检查的范围。
有几个选项,dasblinkenlight的解决方案可能是最优雅的。 (您可能无法更改方法签名,例如,如果要覆盖inheritance的add
方法,或计划传递add
方法等)。
另一个可能有用的选项是使用有界类型参数而不是无界参数。 由于类型擦除,无限制类型参数在编译后完全丢失,但使用有界类型参数将用它/必须扩展的那些替换generics类型的实例。
class Test {
当然, T
在这一点上并不是真正的通用,但使用此类定义将在运行时强制执行类型,因为将根据Number
超类检查强制转换。 这是certificate它的字节码:
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
尝试添加字符串时,此类定义会生成所需的ClassCastException
。