gre*_*eth 19 java generics casting
我有以下类(简化但仍然是一个工作示例):
class Test<T> {
List<T> l = new ArrayList<>();
public Test() {
}
public void add(Object o) {
l.add((T)o);
}
}
Run Code Online (Sandbox Code Playgroud)
和测试代码:
Test<Double> t = new Test<>();
t.add(1);
t.add(1.2);
t.add(-5.6e-2);
t.add("hello");
Run Code Online (Sandbox Code Playgroud)
一切都很好,这不是我所期待的.该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
Run Code Online (Sandbox Code Playgroud)
为什么仅在变量赋值时抛出异常?如果强制转换(T)
不起作用,我如何强制执行类型一致性?
das*_*ght 21
add方法不应该抛出一个
ClassCastException
?
不,它不应该(虽然我希望它这样做).简而言之,Java实现泛型在编译代码后会丢弃类型信息,因此List<T>
可以接受任何内容Object
,并且add
不会检查方法中的强制转换.
为什么仅在变量赋值时抛出异常?
因为Double
那里的强制转换是由编译器插入的.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));
}
}
Run Code Online (Sandbox Code Playgroud)
现在,强制转换是由Class<T>
对象执行的,因此您将ClassCastException
尝试插入不正确类型的对象.
作为替代解决方案,您可以使用Collections.checkedList
:
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);
}
}
Run Code Online (Sandbox Code Playgroud)
这样您将获得以下异常:
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)
Run Code Online (Sandbox Code Playgroud)
为了完成这个资源,这里是一个强制类型转换为通用的编译字节码的区别:
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
Run Code Online (Sandbox Code Playgroud)
并且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
Run Code Online (Sandbox Code Playgroud)
您可以看到带有泛型的版本根本不执行该checkcast
指令(由于类型擦除,因此在为数据提供不匹配的类时,您不应该期待异常.不幸的是,这不是更严格的强制执行,但这是有道理的,因为泛型用于进行更严格的编译时类型检查,并且由于类型擦除而在运行时没有太大帮助.
Java将检查函数参数的类型,以查看是否存在类型匹配,或者是否可以执行类型提升.在您的情况下,String
是参数的类型,并且可以提升为Object
,这是确保函数调用有效的编译时类型检查的范围.
有几个选项,dasblinkenlight的解决方案可能是最优雅的.(您可能无法更改方法签名,例如,如果要覆盖继承的add
方法,或计划传递add
方法等).
另一个可能有用的选择是使用有界类型参数而不是无界参数.由于类型擦除,无限制类型参数在编译后完全丢失,但使用有界类型参数将用它/必须扩展的那些实例替换泛型类型的实例.
class Test<T extends Number> {
Run Code Online (Sandbox Code Playgroud)
当然,此时T
并不是真正的通用,但使用此类定义将在运行时强制执行类型,因为将针对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
Run Code Online (Sandbox Code Playgroud)
ClassCastException
在尝试添加字符串时,此类定义会生成所需的内容.
归档时间: |
|
查看次数: |
13064 次 |
最近记录: |