下界通配符会在 javac 中引起问题,但不会在 Eclipse 中引起问题
Lower-bounded wild card causes trouble in javac, but not Eclipse
这段代码在 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)
这段代码在 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 → Consumer<? super T#0>⟩
逐步减少(通过应用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 ?
最后一行是“类型限制”并完成了缩减。由于不涉及进一步的约束,解析简单地将 T#0
实例化为 capture#1-of ?
.
类型
通过这些步骤,类型推断已证明 m2
适用于此特定调用。 qed.
直观地,显示的解决方案告诉我们:无论捕获可能代表什么类型,如果 T
设置为代表完全相同的类型,则不会违反任何类型约束。这是可能的,因为捕获是固定的 before starting type inference.
请注意,以下内容可以毫无问题地编译:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
}
}
尽管我们不知道消费者的实际类型,但我们知道它将可分配给 Consumer<T>
,尽管我们不知道 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) {
}
}
没有编译器反对。
当您将通配符替换为命名类型时,它也会被接受,例如
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
的错误报告,但是当涉及到 javac
和通配符类型时,它们太多了,我最终放弃了。免责声明:这并不意味着有这么多的错误,只是报告了这么多相关的场景,这可能都是同一个错误的不同症状。
唯一重要的是,它已经在 Java 9 中修复。
这段代码在 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)
这段代码在 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 → Consumer<? super T#0>⟩
逐步减少(通过应用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 ?
最后一行是“类型限制”并完成了缩减。由于不涉及进一步的约束,解析简单地将 T#0
实例化为 capture#1-of ?
.
通过这些步骤,类型推断已证明 m2
适用于此特定调用。 qed.
直观地,显示的解决方案告诉我们:无论捕获可能代表什么类型,如果 T
设置为代表完全相同的类型,则不会违反任何类型约束。这是可能的,因为捕获是固定的 before starting type inference.
请注意,以下内容可以毫无问题地编译:
public class Test {
public static final void m1(Consumer<?> c) {
m2(c);
}
private static final <T> void m2(Consumer<T> c) {
}
}
尽管我们不知道消费者的实际类型,但我们知道它将可分配给 Consumer<T>
,尽管我们不知道 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) {
}
}
没有编译器反对。
当您将通配符替换为命名类型时,它也会被接受,例如
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
的错误报告,但是当涉及到 javac
和通配符类型时,它们太多了,我最终放弃了。免责声明:这并不意味着有这么多的错误,只是报告了这么多相关的场景,这可能都是同一个错误的不同症状。
唯一重要的是,它已经在 Java 9 中修复。