Lower-bounded wild card causes trouble in javac, but not Eclipse

删除回忆录丶 提交于 2019-12-03 06:00:50

This code is legal wrt JLS 8. javac version 8 and earlier had several bugs in how they handle wildcards and captures. Starting with version 9 (early access, I tried version ea-113 and newer) also javac accepts this code.

To understand how a compiler analyzes this according to JLS, it is essential to tell apart what are wildcard captures, type variables, inference variables and such.

The type of c is Consumer<capture#1-of ?> (javac would write Consumer<CAP#1>). This type is unknown, but fixed.

The parameter of m2 has type Consumer<? super T>, where T is a type variable to be instantiated by type inference.

During type inference an inference variable, denoted by ecj as T#0, is used to represent T.

Type inference consists in determining, whether T#0 can be instantiated to any type without violating any given type constraints. In this particular case we start with this contraint:

⟨c → Consumer<? super T#0>⟩

Which is stepwise reduced (by applying JLS 18.2):

⟨Consumer<capture#1-of ?> → Consumer<? super T#0>⟩

⟨capture#1-of ? <= ? super T#0⟩

⟨T#0 <: capture#1-of ?⟩

T#0 <: capture#1-of ?

The last line is a "type bound" and reduction is done. Since no further constraints are involved, resolution trivially instantiates T#0 to the type capture#1-of ?.

By these steps, type inference has proven that m2 is applicable for this particular invocation. qed.

Intuitively, the shown solution tells us: whatever type the capture may represent, if T is set to represent the exact same type, no type constraints are violated. This is possible, because the capture is fixed before starting type inference.

Note that the following can be compiled without problems:

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<T> c) {
    }
}

Despite we don’t know the actual type of the consumer, we know that it will be assignable to Consumer<T>, though we don’t know what T is (not knowing what T is, is the norm in generic code anyway).

But if the assignment to Consumer<T> is valid, the assignment to Consumer<? super T> would be as well. We can even show this practically with an intermediate step:

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<T> c) {
        m3(c);
    }
    private static final <T> void m3(Consumer<? super T> c) {
    }
}

No compiler objects that.

It will also be accepted when you replace the wild card with a named type, e.g.

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <E,T extends E> void m2(Consumer<E> c) {
    }
}

Here, E is a super type of T, just like ? super T is.

I tried to find the bug report for javac closest to this scenario, but when it comes to javac and wildcard types, there are so many of them, that I eventually gave up. Disclaimer: this does not imply that there are so many bugs, just that there are so many related scenarios reported, which may all be different symptoms of the same bug.

The only thing that matters, is, that it is already fixed in Java 9.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!