java 通用类型转换
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/31960987/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Generic Type cast
提问by gregseth
I have the following class (simplified but still a working example):
我有以下课程(简化但仍然是一个工作示例):
class Test<T> {
List<T> l = new ArrayList<>();
public Test() {
}
public void add(Object o) {
l.add((T)o);
}
}
And the test code:
和测试代码:
Test<Double> t = new Test<>();
t.add(1);
t.add(1.2);
t.add(-5.6e-2);
t.add("hello");
Everything is working fine, and that's not what I was expecting. Shouldn't the add
method throw a ClassCastException
? If I add a get
method that's more or less the same thing:
一切正常,这不是我所期望的。该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
Why is it only on variable assignment that the exception is thrown? How can I enforce type consistency if the (T)
cast doesn't work?
为什么只有在变量赋值时才会抛出异常?如果强制转换(T)
不起作用,我如何强制类型一致性?
回答by dasblinkenlight
Shouldn't the add method throw a
ClassCastException
?
add 方法不应该抛出一个
ClassCastException
吗?
No, it shouldn't (although I wish it did). Long story short, Java implementation of generics discards type information after compiling your code, so List<T>
is allowed to take any Object
, and the cast inside your add
method is not checked.
不,它不应该(虽然我希望它这样做)。长话短说,泛型的 Java 实现在编译您的代码后会丢弃类型信息,因此List<T>
允许使用 any Object
,并且add
不会检查您的方法中的强制转换。
Why is it only on variable assignment that the exception is thrown?
为什么只有在变量赋值时才会抛出异常?
Because the cast to Double
there is inserted by the compiler. Java compiler knows that the return type of get
is T
, which is Double
, so it inserts a cast to match the type of the variable d
, to which the result is being assigned.
因为到Double
那里的演员表是由编译器插入的。Java 编译器知道get
is的返回类型T
,即 is Double
,因此它插入一个强制转换以匹配d
要分配结果的变量的类型。
Here is how you can implement a generic-safe cast:
以下是实现泛型安全转换的方法:
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));
}
}
Now the cast is performed by a Class<T>
object, so you will get a ClassCastException
on an attempt to insert an object of an incorrect type.
现在转换是由Class<T>
对象执行的,因此您将ClassCastException
尝试插入类型不正确的对象。
回答by Tagir Valeev
As an alternative solution you can use Collections.checkedList
:
作为替代解决方案,您可以使用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);
}
}
This way you will get the following 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)
回答by Purag
For the completeness of this resource, here's the difference in compiled bytecode between a cast to a generic:
为了该资源的完整性,以下是转换为泛型之间编译字节码的差异:
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
And an explicit cast to a Double
with no 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
You can see that the version with generics doesn't perform the checkcast
instruction at all (thanks to type erasure, so you shouldn't expect an exception when giving it data with an unmatched class. It's unfortunate that this isn't more strictly enforced, but this makes sense as generics are for making stricter compile-timetype checks, and aren't much help at runtime due to type erasure.
您可以看到具有泛型的版本根本不执行checkcast
指令(由于类型擦除,因此在向其提供具有不匹配类的数据时不应期望异常。不幸的是,这并没有得到更严格的执行,但这是有道理的,因为泛型用于进行更严格的编译时类型检查,并且由于类型擦除而在运行时没有太大帮助。
Java will check the types of function arguments to see if there is a type match, or if a type promotion can be performed. In your case, String
is the type of the argument, and that can be promoted to Object
, which is the extent of the compile-time type checks that ensure that function call works.
Java 将检查函数参数的类型以查看是否存在类型匹配,或者是否可以执行类型提升。在您的情况下,String
是参数的类型,可以提升为Object
,这是确保函数调用有效的编译时类型检查的范围。
There are a few options, and dasblinkenlight's solution is probably the most elegant. (You may not be able to change the method signature, say, if you are overriding an inherited add
method, or plan on passing down the add
method, etc).
有几个选项,dasblinkenlight 的解决方案可能是最优雅的。(您可能无法更改方法签名,例如,如果您要覆盖继承的add
方法,或计划传递该add
方法等)。
Another option that may help is using a boundedtype parameter instead of an unbounded one. Unbounded type parameters are completely lost after compilation due to type erasure, but using a bounded type parameter will replace instances of the generic type with that/those it must extend.
另一个可能有帮助的选项是使用有界类型参数而不是无界参数。由于类型擦除,无界类型参数在编译后完全丢失,但使用有界类型参数将用它必须扩展的实例替换泛型类型的实例。
class Test<T extends Number> {
Of course, T
is not truly generic at this point, but using this class definition will enforce types at runtime since the cast willbe checked against the Number
superclass. Here's the bytecode to prove it:
当然,此时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
This class definition generates the desired ClassCastException
when trying to add the string.
此类定义ClassCastException
在尝试添加字符串时生成所需的。