较低限度的外卡会导致javac出现问题,但Eclipse不会出现问题

Luk*_*der 17 java eclipse generics javac java-8

这段代码在Eclipse中编译,但不在javac中编译:

import java.util.function.Consumer;

public class Test {
    public static final void m1(Consumer<?> c) {
        m2(c);
    }
    private static final <T> void m2(Consumer<? super T> c) {
    }
}
Run Code Online (Sandbox Code Playgroud)

javac输出:

C:\Users\lukas\workspace>javac -version
javac 1.8.0_92

C:\Users\lukas\workspace>javac Test.java
Test.java:5: error: method m2 in class Test cannot be applied to given types;
        m2(c);
        ^
  required: Consumer<? super T>
  found: Consumer<CAP#1>
  reason: cannot infer type-variable(s) T
    (argument mismatch; Consumer<CAP#1> cannot be converted to Consumer<? super T>)
  where T is a type-variable:
    T extends Object declared in method <T>m2(Consumer<? super T>)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error
----------------------------------------------------------------
Run Code Online (Sandbox Code Playgroud)

哪个编译器错了,为什么?(Eclipse错误报告和部分讨论在这里)

Ste*_*ann 11

这段代码与JLS 8合法.javac版本8及更早版本在如何处理通配符和捕获方面存在一些错误.从版本9(早期访问,我尝试版本ea-113和更新版本)开始,javac也接受此代码.

要了解编译器如何根据JLS对此进行分析,必须分清哪些是通配符捕获,类型变量,推理变量等.

类型cConsumer<capture#1-of ?>(javac会写Consumer<CAP#1>).此类型未知,但已修复.

m2has类型的参数Consumer<? super T>,where T是要通过类型推断实例化的类型变量.

在类型推断期间,使用由ecj as表示的推理变量T#0来表示T.

类型推断在于确定是否T#0可以在不违反任何给定类型约束的情况下实例化为任何类型.在这个特殊情况下,我们从这个约束开始:

⟨c→消费者<?超级T#0>⟩

逐步减少(通过应用JLS 18.2):

⟨消费者<捕获#1 - >?>消费者<?超级T#0>⟩

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

⟨T#0 <:捕获#1-of?⟩

T#0 <:捕获#1?

最后一行是"类型绑定"并完成缩减.由于不涉及进一步的约束,因此解决方案可以简单地实例化T#0到该类型capture#1-of ?.

通过这些步骤,已经证明类型推断m2适用于此特定调用.QED.

直观地,所示解决方案告诉我们:无论捕获可以表示什么类型,如果T设置为表示完全相同的类型,则不违反类型约束.这是可能的,因为开始类型推断之前捕获是固定的.

  • 我也很高兴看到这种趋同:) (2认同)

Hol*_*ger 6

请注意,可以编译以下内容而不会出现问题:

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

尽管我们不知道消费者的实际类型,但我们知道它可以分配Consumer<T>,但我们不知道是什么T(不知道什么T是通用代码的规范).

但是如果赋值Consumer<T>是有效的,那么赋值Consumer<? super T>也是如此.我们甚至可以通过一个中间步骤来实现这一点:

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) {
    }
}
Run Code Online (Sandbox Code Playgroud)

没有编译器对象.

当您使用命名类型替换通配符时,也会接受它,例如

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

在这里,E是一种超级类型T,就像? super T是.

我试图找到javac最接近这种情况的错误报告,但是当涉及到javac和通配符类型时,它们有很多,我最终放弃了.免责声明:这并不意味着有太多的错误,只是报告了很多相关的情况,这可能都是同一个错误的不同症状.

唯一重要的是,它已经在Java 9中得到修复.