首页 > 解决方案 > 将此对象转换为泛型类型如何工作?

问题描述

我的理解是泛型类型是不变的,所以如果我们有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 是否以某种方式相关?在这种情况下,铸造如何成功?

标签: javagenericscastingeffective-java

解决方案


这个强制转换编译,因为它是缩小转换的一个特例。(根据§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 节)。YSXYXY

    使用java.util包中的类型作为示例,不存在从ArrayList<String>to的缩小引用转换ArrayList<Object>,反之亦然,因为类型参数StringObject可证明是不同的。ArrayList<String>出于同样的原因,从到不存在缩小引用转换List<Object>,反之亦然。拒绝可证明不同的类型是一个简单的静态门,以防止“愚蠢的”缩小引用转换。

换句话说,如果ab具有具有相同擦除的公共超类型(在这种情况下,例如List),那么它们必须是 JLS 所称的“可证明不同”,由§4.5给出:

如果以下任一情况为真,则可证明两种参数化类型是不同的:

  • 它们是不同泛型类型声明的参数化。

  • 它们的任何类型参数都可以证明是不同的。

§4.5.1

如果以下条件之一为真,则两个类型参数可证明是不同的:

  • 两个参数都不是类型变量或通配符,并且两个参数不是同一类型。

  • 一个类型参数是类型变量或通配符,其上限(来自捕获转换,如有必要)为S; 并且另一个类型参数T不是类型变量或通配符;既不是|S| <: |T|也不是|T| <: |S|

  • 每个类型参数都是一个类型变量或通配符,其上限(来自捕获转换,如有必要)为Sand T; 既不是|S| <: |T|也不是|T| <: |S|

因此,鉴于上述规则,List<String>并且List<Double> 可以证明是不同的(通过 4.5.1 中的第一条规则),因为StringDouble是不同的类型参数。

但是,UnaryOperator<T>不能UnaryOperator<Object>证明是不同的(通过 4.5.1 中的第二条规则),因为:

  1. 一个类型参数是类型变量 ( T,上限为Object.)

  2. 该类型变量的边界与另一个类型 ( 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>,我们可以相当权威地说它总是错误的。


推荐阅读