将此对象转换为通用类型如何工作?

How does casting this object to a generic type work?

我的理解是泛型类型是 不变的,所以如果我们有 B 作为 A 的子类型,那么 List<B> 没有与 List<A> 的关系。所以投射不适用于 List<A>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));
    }
}

在这里我很困惑。我们将类型为 UnaryOperator<Object>IDENTIFY_FN 转换为具有另一个类型参数的 UnaryOperator<T>

当类型擦除发生时 String 是 Object 的子类型,但据我所知 UnaryOperator<String> 不是 UnaryOperator<Object>.

的子类型

Object 和 T 有某种关联吗?在这种情况下,铸造是如何成功的?

演员阵容

return (UnaryOperator<T>) IDENTIFY_FN;

基本上相当于对原始类型 UnaryOperator 的强制转换,因为 T 在运行时被擦除,并且在编译时出于强制转换的目的而被忽略。您 可以 将泛型类型转换为其原始类型(出于向后兼容性原因),但您应该收到 "unchecked" 警告。

这也可以,例如:

UnaryOperator<String> foo = (UnaryOperator) IDENTITY_FN;

泛型在运行时不存在。在运行时,每个 UnaryOperator<T> 是一个UnaryOperator<Object>。强制转换对于在编译时安抚编译器是必要的。在运行时它是没有意义的。

JLS允许这样的转换:

A cast from a type S to a parameterized type T is unchecked unless at least one of the following conditions holds:

  • S <: T

  • All of the type arguments of T are unbounded wildcards.

  • [ ... ]

因此,未经检查的转换会导致编译时未经检查的警告,除非被 SuppressWarnings 注释抑制。

此外,在类型擦除过程中,identifyFunctionIDENTIFY_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 成功,因为恒等函数 returns 其参数未修改。

此转换可以编译,因为它是缩小转换的特例。 (根据 §5.5,缩小转换是强制转换允许的转换类型之一,因此此答案的大部分内容将集中在缩小转换的规则上。)

请注意,虽然 UnaryOperator<T> 不是 UnaryOperator<Object> 的子类型(因此转换不是 "downcast"),但它仍被视为缩小转换。来自 §5.6.1:

A narrowing reference conversion treats expressions of a reference type S as expressions of a different reference type T, where S is not a subtype of T. [...] Unlike widening reference conversion, the types need not be directly related. However, there are restrictions that prohibit conversion between certain pairs of types when it can be statically proven that no value can be of both types.

其中一些 "sideways" 转换由于特殊规则而失败,例如以下将失败:

List<String> a = ...;
List<Double> b = (List<String>) a;

具体来说,这是由 §5.1.6.1 中的规则给出的,其中规定:

  • If there exists a parameterized type X that is a supertype of T, and a parameterized type Y that is a supertype of S, such that the erasures of X and Y are the same, then X and Y are not provably distinct (§4.5).

    Using types from the java.util package as an example, no narrowing reference conversion exists from ArrayList<String> to ArrayList<Object>, or vice versa, because the type arguments String and Object are provably distinct. For the same reason, no narrowing reference conversion exists from ArrayList<String> to List<Object>, or vice versa. The rejection of provably distinct types is a simple static gate to prevent "stupid" narrowing reference conversions.

换句话说,如果 ab 有一个具有相同擦除的公共超类型(在这种情况下,例如 List),那么它们必须是JLS 正在调用 "provably distinct",由 §4.5:

给出

Two parameterized types are provably distinct if either of the following is true:

  • They are parameterizations of distinct generic type declarations.

  • Any of their type arguments are provably distinct.

§4.5.1

Two type arguments are provably distinct if one of the following is true:

  • Neither argument is a type variable or wildcard, and the two arguments are not the same type.

  • One type argument is a type variable or wildcard, with an upper bound (from capture conversion, if necessary) of S; and the other type argument T is not a type variable or wildcard; and neither |S| <: |T| nor |T| <: |S|.

  • Each type argument is a type variable or wildcard, with upper bounds (from capture conversion, if necessary) of S and T; and neither |S| <: |T| nor |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 绝对 不是' tObject。例如,我们可能有这样的情况:

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> 的情况,我们可以非常权威地说它总是错误的。