java - 将此对象转换为泛型类型如何工作?
问题描述
我的理解是泛型类型是不变的,所以如果我们有B
作为子类型的A
,那么List<B>
就跟没有关系了List<A>
。所以铸造不会在List<A>
and上工作List<B>
。
在 Effective Java 第三版中,我们有这段代码:
// 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));
}
}
在这里我很困惑。我们有 cast IDENTIFY_FN
,它的类型是UnaryOperator<Object>
, to UnaryOperator<T>
,它有另一个类型参数。
当类型擦除发生时,String 是 Object 的子类型,但据我所知UnaryOperator<String>
不是UnaryOperator<Object>
.
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
参数化类型,使得和的擦除相同,则和不可证明是不同的(第4.5 节)。Y
S
X
Y
X
Y
使用
java.util
包中的类型作为示例,不存在从ArrayList<String>
to的缩小引用转换ArrayList<Object>
,反之亦然,因为类型参数String
和Object
可证明是不同的。ArrayList<String>
出于同样的原因,从到不存在缩小引用转换List<Object>
,反之亦然。拒绝可证明不同的类型是一个简单的静态门,以防止“愚蠢的”缩小引用转换。
换句话说,如果a
和b
具有具有相同擦除的公共超类型(在这种情况下,例如List
),那么它们必须是 JLS 所称的“可证明不同”,由§4.5给出:
如果以下任一情况为真,则可证明两种参数化类型是不同的:
它们是不同泛型类型声明的参数化。
它们的任何类型参数都可以证明是不同的。
和§4.5.1:
如果以下条件之一为真,则两个类型参数可证明是不同的:
两个参数都不是类型变量或通配符,并且两个参数不是同一类型。
一个类型参数是类型变量或通配符,其上限(来自捕获转换,如有必要)为
S
; 并且另一个类型参数T
不是类型变量或通配符;既不是|S| <: |T|
也不是|T| <: |S|
。每个类型参数都是一个类型变量或通配符,其上限(来自捕获转换,如有必要)为
S
andT
; 既不是|S| <: |T|
也不是|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>
,我们可以相当权威地说它总是错误的。
推荐阅读
- ibm-cloud - 创建公共操作不会添加到序列中
- r - 在 R Shiny 中,如何使用在该 observeEvent 之外的 observeEvent(按钮单击)中生成的数据?
- javascript - HTML/JS Select onchange 不适用于某些选择项
- python - 选择带有 selenium python 问题的元素
- r - 调用编译后的 R 函数
- c++ - str.length() 如何计算字符串长度()?
- database - 在 Tableau 中比较两个数据集
- c++ - stoi, stol, stoll 将最大大小数据类型的字符串转换为 int 数据类型的限制
- javascript - 计算 x,y 坐标,考虑旋转
- javascript - 组件未使用“useEffect”挂钩在 React 中重新渲染