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

时间:2016-07-07 13:15:07

标签: java eclipse generics java-8 javac

这段代码在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) {
    }
}

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
----------------------------------------------------------------

哪个编译器错了,为什么? (Eclipse bug report and parts of discussion here

2 个答案:

答案 0 :(得分:10)

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

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

c的类型为Consumer<capture#1-of ?>(javac会写Consumer<CAP#1>)。这种类型未知,但已修复。

m2的参数类型为Consumer<? super T>,其中T是要通过类型推断实例化的类型变量。

在类型推断期间,推理变量(由ecj表示为T#0)用于表示T

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

  

⟨c→消费者&lt;?超级T#0&gt;⟩

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

  

⟨Consumer&lt; capture#1-of?&gt; →消费者&lt;?超级T#0&gt;⟩

     

⟨capture#1-of? &lt; =?超级T#0⟩

     

⟨T#0&lt ;:捕获#1-of?⟩

     

T#0&lt ;: capture#1-of?

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

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

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

答案 1 :(得分:6)

请注意,可以毫无问题地编译以下内容:

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

尽管我们不知道消费者的实际类型,但我们知道它可以分配给Consumer<T>,尽管我们不知道T是什么(不知道是什么{{1}无论如何,这是通用代码中的标准。)

但如果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) {
    }
}

此处,public class Test { public static final void m1(Consumer<?> c) { m2(c); } private static final <E,T extends E> void m2(Consumer<E> c) { } } E的超级类型,就像T一样。

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

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