[英]How does casting this object to a generic type work?
我的理解是泛型类型是不变的 ,所以如果我们将B
作为A
的子类型,那么List<B>
与List<A>
没有关系。 因此,对于List<A>
和List<B>
强制转换不起作用。
从Effective Java Third Edition我们有这段代码:
// Generic singleton factory pattern
private static UnaryOperator<Object> IDENTIFY_FN = (t) -> t;
@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identifyFunction() {
return (UnaryOperator<T>) IDENTIFY_FN; //OK But how, why?
}
public static void main(String[] args) {
String[] strings = {"a", "b", "c"};
UnaryOperator<String> sameString = identifyFunction();
for (String s : strings) {
System.out.println(sameString.apply(s));
}
}
我在这里很困惑。 我们将IDENTIFY_FN
(其类型为UnaryOperator<Object>
)转换为UnaryOperator<T>
,它具有另一个类型参数。
当类型擦除发生时String是Object的子类型,但据我所知, UnaryOperator<String>
不是UnaryOperator<Object>
的子类型。
对象和T以某种方式相关吗? 在这种情况下,铸造如何成功?
此演员表编译,因为它是缩小转换的特例。 (根据§5.5 ,缩小转化次数是演员所允许的转换类型之一,因此大多数答案都将侧重于缩小转换的规则。)
请注意,虽然UnaryOperator<T>
不是UnaryOperator<Object>
的子类型(因此转换不是“向下转换”),但它仍被视为缩小转换。 从§5.6.1开始 :
缩小引用转换将引用类型
S
表达式视为不同引用类型T
表达式,其中S
不是T
的子类型。 [...]与扩展参考转换不同,类型不需要直接相关。 但是,当可以静态地证明两种类型都没有值时,存在禁止在某些类型对之间进行转换的限制。
由于特殊规则,这些“横向”演员中的一些失败,例如以下将失败:
List<String> a = ...;
List<Double> b = (List<String>) a;
具体而言,这是由第5.1.6.1节中的规则给出的,其中规定:
如果存在参数化类型
X
是T
的超类型,参数化类型Y
是S
的超类型,使得X
和Y
的擦除相同,则X
和Y
不可证明是不同的( §4.5 ) 。使用
java.util
包中的类型作为示例,从ArrayList<String>
到ArrayList<Object>
不存在缩小引用转换,反之亦然,因为类型参数String
和Object
可证明是不同的。 出于同样的原因,从ArrayList<String>
到List<Object>
不存在缩小引用转换,反之亦然。 拒绝可证明的不同类型是一个简单的静态门,以防止“愚蠢”缩小参考转换。
换句话说,如果a
和b
具有相同擦除的公共超类型(在这种情况下,例如, List
),则它们必须是JLS所称的“可证明不同”,由§4.5给出:
如果满足以下任一条件,则两个参数化类型可证明是不同的:
它们是不同泛型类型声明的参数化。
他们的任何类型参数都是明显不同的。
并且§4.5.1 :
如果满足下列条件之一,则两个类型参数可证明是不同的 :
两个参数都不是类型变量或通配符,并且这两个参数的类型不同。
一种类型的参数是类型变量或通配符,具有
S
的上限(如果需要,来自捕获转换); 而另一个类型参数T
不是类型变量或通配符; 既不是|S| <: |T|
|S| <: |T|
也|T| <: |S|
|T| <: |S|
。每个类型参数都是一个类型变量或通配符,具有
S
和T
上限(如果需要,来自捕获转换); 既不是|S| <: |T|
|S| <: |T|
也|T| <: |S|
|T| <: |S|
。
因此,鉴于上面的规则, List<String>
和List<Double>
是可证明不同(通过从4.5.1第一规则),因为String
和Double
是不同类型的参数。
但是, UnaryOperator<T>
和UnaryOperator<Object>
不可证明(通过4.5.1中的第二条规则),因为:
一个类型参数是一个类型变量( T
,上限为Object
。)
该类型变量的边界与另一种类型( Object
)的类型参数相同。
由于UnaryOperator<T>
和UnaryOperator<Object>
不可分辨,因此允许缩小转换,因此转换编译。
考虑编译器为什么允许其中某些转换而不允许其他转换的一种方法是:在类型变量的情况下,它不能证明T
肯定不是 Object
。 例如,我们可能会遇到这样的情况:
UnaryOperator<String> aStringThing = Somewhere::doStringThing;
UnaryOperator<Double> aDoubleThing = Somewhere::doDoubleThing;
<T> UnaryOperator<T> getThing(Class<T> t) {
if (t == String.class)
return (UnaryOperator<T>) aStringThing;
if (t == Double.class)
return (UnaryOperator<T>) aDoubleThing;
return null;
}
在这些情况下,我们实际上知道演员阵容是正确的,只要没有其他人做一些有趣的事情(比如未经检查的转换Class<T>
参数)。
因此,在转换为UnaryOperator<T>
的一般情况下,我们实际上可能正在做一些合法的事情。 相比之下,在将List<String>
为List<Double>
,我们可以非常权威地说它总是错误的。
JLS允许这样的演员:
除非至少满足下列条件之一,否则将取消选中从类型S到参数化类型T的强制转换 :
S <: T
T的所有类型参数都是无界通配符。
[...]
因此,未经检查的强制转换会导致编译时未经检查的警告,除非被SuppressWarnings
注释SuppressWarnings
。
此外,在类型擦除过程中, identifyFunction
和IDENTIFY_FN
编译为:
private static UnaryOperator IDENTIFY_FN;
public static UnaryOperator identifyFunction() {
return IDENTIFY_FN; // cast is removed
}
并将checkcast
添加到呼叫站点:
System.out.println(sameString.apply(s));
^
INVOKEINTERFACE java/util/function/UnaryOperator.apply (Ljava/lang/Object)Ljava/lang/Object
CHECKCAST java/lang/String
INVOKEVIRTUAL java/io/PrintStream.println (Ljava/lang/String;)V
checkcast
成功,因为identity函数返回其参数未修改。
泛型在运行时不存在。 在运行时, 每个 UnaryOperator<T>
都是一个UnaryOperator<Object>
。 在编译时安装编译器是必要的。 在运行时它没有意义。
演员
return (UnaryOperator<T>) IDENTIFY_FN;
基本上相当于对原始类型UnaryOperator
进行强制转换,因为T
在运行时被擦除并在编译时被忽略以用于强制转换。 您可以将泛型类型转换为其原始类型(出于向后兼容性原因),但您应该获得“未选中”警告。
这也可以,例如:
UnaryOperator<String> foo = (UnaryOperator) IDENTITY_FN;
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.