如何将具有边界的泛型返回类型的方法分配给该边界之外的变量?

Pau*_*oek 6 java generics inheritance type-erasure

假设我有以下结构:

public interface A {
}

public interface B {
}

public interface B1 extends B {
}

public interface B2 extends B {
}

public class C implements A, B1 {
    private final String s;

    public C(final String s) {
        this.s = s;
    }
}

public class D implements A, B2 {
    private final Exception e;

    public D(final Exception e) {
        this.e = e;
    }
}

public class SomeClass<T> {
    private final T t;
    private final Exception e;

    public SomeClass(final T t, final Exception e) {
        this.t = t;
        this.e = e;
    }

    public <U extends B> U transform(final java.util.function.Function<T, ? extends U> mapper1, final java.util.function.Function<Exception, ? extends U> mapper2) {
        return t == null ? mapper2.apply(e) : mapper1.apply(t);
    }
}
Run Code Online (Sandbox Code Playgroud)

现在我们在另一个类中执行以下操作:

public class AnotherClass {
    public static void main(final String[] args) {
        SomeClass<String> someClass = new SomeClass<>("Hello World!", null);
// this line is what is bothering me
        A mappedResult = someClass.transform(C::new, D::new);
    }
}
Run Code Online (Sandbox Code Playgroud)

代码编译没有任何问题。为什么要编译代码?即使方法中的泛型 U 被声明为 B 的子类型,“mappedResult”的类型怎么可能是 A?

Pau*_*oek 0

好吧,根据对这个问题的评论以及与其他人的一些讨论,我错过了一个可能需要解决的要点,这实际上解释了评论中给出的答案。

很明显,以下内容可以编译:

Object mappedResult = someClass.transform(C::new, D::new);
Run Code Online (Sandbox Code Playgroud)

当然,Object 并不是 B 的子类。绑定将确保 C 和 D 的类型(在本例中)将是 B 的子类型,但由于 C 和 D 实现的其他接口,它们也可以是其他类型。编译器将检查它们是什么类型,并查看它们共有的最具体的类型。在本例中,既是 A 又是 B,因此类型被派生为 A & B。因此,将此结果分配给 A 是可能的,因为编译器也会将结果派生为 A。

界限确实提供了一些有关输入的限制,但不涉及输出,也不涉及可将结果分配给的变量类型。这也是我之前很困惑的地方。

另一种查看方法如下:如果该方法定义如下:

public <U> U transform(final java.util.function.Function<T, ? extends U> mapper1, final java.util.function.Function<Exception, ? extends U> mapper2) {
    return t == null ? mapper2.apply(e) : mapper1.apply(t);
}
Run Code Online (Sandbox Code Playgroud)

那么调用时仍然可以将结果赋值给 A 或 B。界限对此没有影响。它在这里确保的是两个映射器函数都需要映射到 U 子类型的结果。通过绑定,它成为 U 的子类型,而 U 是 B 的子类型。但事实是结果是 A 的子类型不会改变它也是 B 的子类型的事实。因此,结果可以分配给任一类型。