为什么这个泛型方法可以绑定 return 任何类型?

Why can this generic method with a bound return any type?

为什么下面的代码可以通过? 方法 IElement.getX(String) return 是类型 IElement 或其子 class 类型的实例。 Main class 中的代码调用 getX(String) 方法。编译器允许将 return 值存储到 Integer 类型的变量中(显然不在 IElement 的层次结构中)。

public interface IElement extends CharSequence {
  <T extends IElement> T getX(String value);
}

public class Main {
  public void example(IElement element) {
    Integer x = element.getX("x");
  }
}

return 类型不应该仍然是 IElement 的实例 - 即使在类型擦除之后?

getX(String)方法的字节码是:

public abstract <T extends IElement> T getX(java.lang.String);
flags: ACC_PUBLIC, ACC_ABSTRACT
Signature: #7                           // <T::LIElement;>(Ljava/lang/String;)TT;

编辑:String 一致地替换为 Integer

这实际上是一个合法的类型推断*。

我们可以将其简化为以下示例 (Ideone):

interface Foo {
    <F extends Foo> F bar();
    
    public static void main(String[] args) {
        Foo foo = null;
        String baz = foo.bar();
    }
}

允许编译器推断(荒谬的,真的)交集类型 String & Foo 因为 Foo 是一个接口。对于问题中的示例,推断出 Integer & IElement

这是无意义的,因为转换是不可能的。我们不能自己做这样的转换:

// won't compile because Integer is final
Integer x = (Integer & IElement) element;

类型推断基本上适用于:

  • 一组推理变量,用于每个方法的类型参数。
  • 必须遵守的一组边界
  • 有时 约束减少到界限。

在算法结束时,每个变量解析为基于绑定集的交集类型,如果它们有效,则调用编译。

进程开始于 8.1.3:

When inference begins, a bound set is typically generated from a list of type parameter declarations P<sub>1</sub>, ..., P<sub>p</sub> and associated inference variables α<sub>1</sub>, ..., α<sub>p</sub>. Such a bound set is constructed as follows. For each l (1 ≤ l ≤ p):

  • […]

  • Otherwise, for each type T delimited by & in a TypeBound, the bound α<sub>l</sub> <: T[P<sub>1</sub>:=α<sub>1</sub>, ..., P<sub>p</sub>:=α<sub>p</sub>] appears in the set […].

因此,这意味着编译器首先以 F <: Foo 的边界开始(这意味着 FFoo 的子类型)。

移动到 18.5.2,return 目标类型被考虑:

If the invocation is a poly expression, […] let R be the return type of m, let T be the invocation's target type, and then:

  • […]

  • Otherwise, the constraint formula ‹R θ → T› is reduced and incorporated with [the bound set].

约束公式 ‹R θ → T› 被简化为 R θ <: T 的另一个边界,所以我们有 F <: String.

稍后根据18.4解决这些问题:

[…] a candidate instantiation T<sub>i</sub> is defined for each α<sub>i</sub>:

  • Otherwise, where α<sub>i</sub> has proper upper bounds U<sub>1</sub>, ..., U<sub>k</sub>, T<sub>i</sub> = glb(U<sub>1</sub>, ..., U<sub>k</sub>).

The bounds α<sub>1</sub> = T<sub>1</sub>, ..., α<sub>n</sub> = T<sub>n</sub> are incorporated with the current bound set.

回想一下,我们的界限集是 F <: Foo, F <: Stringglb(String, Foo) 定义为 String & Foo。这显然是 glb 的合法类型,它只需要:

It is a compile-time error if, for any two classes (not interfaces) V<sub>i</sub> and V<sub>j</sub>, V<sub>i</sub> is not a subclass of V<sub>j</sub> or vice versa.

最后:

If resolution succeeds with instantiations T<sub>1</sub>, ..., T<sub>p</sub> for inference variables α<sub>1</sub>, ..., α<sub>p</sub>, let θ' be the substitution [P<sub>1</sub>:=T<sub>1</sub>, ..., P<sub>p</sub>:=T<sub>p</sub>]. Then:

  • If unchecked conversion was not necessary for the method to be applicable, then the invocation type of m is obtained by applying θ' to the type of m.

因此调用该方法时使用 String & Foo 作为 F 的类型。我们当然可以将其分配给 String,因此不可能将 Foo 转换为 String.

String/Integer 是最终的 类 这一事实显然没有被考虑。


* 注意:键入 erasure is/was 与问题完全无关。

此外,虽然这也可以在 Java 7 上编译,但我认为可以说我们不必担心那里的规范是合理的。 Java 7 的类型推断本质上是 Java 8 的一个不太复杂的版本。它出于类似的原因进行编译。


作为附录,虽然很奇怪,但它可能永远不会导致尚未出现的问题。编写其 return 类型仅从 return 目标推断的泛型方法很少有用,因为只有 null 可以从这样的方法中 returned 而无需强制转换。

假设我们有一些地图模拟,它存储特定接口的子类型:

interface FooImplMap {
    void put(String key, Foo value);
    <F extends Foo> F get(String key);
}

class Bar implements Foo {}
class Biz implements Foo {}

犯如下错误已经完全有效:

FooImplMap m = ...;
m.put("b", new Bar());
Biz b = m.get("b"); // casting Bar to Biz

所以我们可以Integer i = m.get("b");的事实不是新的错误的可能性。如果我们像这样编写代码,那么它一开始就可能不可靠。

通常,如果没有理由绑定类型参数,则只能从目标类型中推断类型参数,例如Collections.emptyList()Optional.empty():

private static final Optional<?> EMPTY = new Optional<>();

public static<T> Optional<T> empty() {
    @SuppressWarnings("unchecked")
    Optional<T> t = (Optional<T>) EMPTY;
    return t;
}

这是 A-OK,因为 Optional.empty() 既不能生产也不能消费 T