此强制转换会编译,因为它是缩小转换范围的特殊情况。(根据 §5.5,缩小转换范围是强制转换允许的转换类型之一,因此此答案的大部分内容将侧重于缩小转换范围的规则。
请注意,虽然它不是 的子类型(因此投射不是“向下转换”),但它仍然被认为是一个缩小的转换。根据 §5.6.1:UnaryOperator<T>
UnaryOperator<Object>
缩小引用范围的转换将引用类型的表达式视为其他引用类型的表达式,其中 不是 的子类型。[...]与加宽引用转换不同,类型不需要直接相关。但是,当可以静态证明没有值可以同时属于这两种类型时,存在禁止在某些类型对之间进行转换的限制。S
T
S
T
其中一些“横向”转换由于特殊规则而失败,例如以下将失败:
List<String> a = ...;
List<Double> b = (List<String>) a;
具体而言,这是由§5.1.6.1中的规则给出的,该规则指出:
-
如果存在一个参数化类型,该类型是 的超类型,而参数化类型是 的超类型,使得 和 的擦除是相同的,则 并且不能证明是不同的 (§4.5)。X
T
Y
S
X
Y
X
Y
以 java.util
包中的类型为例,不存在从 ArrayList<String> 到 ArrayList<
Object>
或反之亦然的缩小引用转换,因为类型参数 String
和 Object
是可证明不同的。出于同样的原因,不存在从 ArrayList<String>
到 List<Object>
或反之亦然的缩小引用转换。拒绝可证明不同的类型是一个简单的静态门,以防止“愚蠢”的缩小参考转换。
换句话说,如果 并且具有具有相同擦除的常见超类型(例如,在这种情况下),则它们必须是 JLS 所称的“可证明不同”,由 §4.5 给出:a
b
List
如果满足以下任一条件,则两个参数化类型可证明是不同的:
它们是不同泛型类型声明的参数化。
它们的任何类型参数都可以证明是不同的。
和 §4.5.1:
如果满足以下条件之一,则两个类型参数可证明是不同的:
这两个参数都不是类型变量或通配符,并且这两个参数不是同一类型。
一个类型参数是类型变量或通配符,其上限(如有必要,从捕获转换)为 ;并且另一个类型参数不是类型变量或通配符;和 既不也不是 。S
T
|S| <: |T|
|T| <: |S|
每个类型参数都是一个类型变量或通配符,其上限(如有必要,从捕获转换开始)为 和 ;和 既不也不是 。S
T
|S| <: |T|
|T| <: |S|
因此,给定上述规则,并且可以证明是不同的(通过4.5.1的第一条规则),因为和是不同的类型参数。List<String>
List<Double>
String
Double
但是,并且不能证明是不同的(通过4.5.1的第二条规则),因为:UnaryOperator<T>
UnaryOperator<Object>
一个类型参数是类型变量(上限为 .)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>
因此,在一般情况下,我们可能实际上正在做一些合法的事情。相比之下,在 cast to 的情况下,我们可以非常权威地说它总是错误的。UnaryOperator<T>
List<String>
List<Double>