Java 通用通配符打破了类型界限
Java Generic Wildcard breaks type bounds
我得到了一个通用 GuiComponent class:
public abstract class GuiComponent<THIS extends GuiComponent<THIS>> extends Gui {
//...
}
(如果你问自己,这个类型参数是干什么的,看)
这个 class 有一堆组件子 class 是这样的:
public class ConcreteComponent extends GuiComponent<ConcreteComponent> {
//...
}
他们都有渲染器:
public interface ComponentRenderer<T extends GuiComponent<T>> {
//...
}
有具体实现:
public class FlatConcreteComponentRenderer implements ComponentRenderer<ConcreteComponent> {
//...
}
但现在我得到了以下 class:
public class GuiListBox<U> extends GuiComponent<GuiListBox<U>> {
//...
}
这本身就是通用的。这导致以下渲染器实现:
public class FlatListBoxRenderer implements ComponentRenderer<GuiListBox<?>> {
//...
}
因为渲染器不需要列表框的类型并且应该用于所有类型的列表框,所以我使用通配符,所以我不必关心类型。在渲染器的 draw 方法中,列表元素只应被视为对象并调用 toString()
。但是这个实现不起作用:
错误:(21, 73) java:类型参数 [...]components.GuiListBox 不在类型变量 T
的范围内
我需要向渲染器添加一个类型,只是为了将其用于 GuiListBox
,然后编译:
public class FlatListBoxRenderer<T> implements ComponentRenderer<GuiListBox<T>>
但这不是很有用,因为默认情况下,同一个渲染器实例将被处理到所有列表框。为什么会出现这个错误,虽然我的 IDE (IntelliJ IDEA) 没有标记这个,但是构建失败?
编辑 #1:我使用 maven 编译项目,但是我的 IDE 和 maven 都无法编译 class。无论如何,这是我的 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.cydhra</groupId>
<artifactId>Utility</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
</dependency>
</dependencies>
</project>
Maven 输出与我的 IDE 构建完全相同的错误。澄清一下:代码有效,如果我向渲染器添加一个类型,但这对我不起作用,因为渲染器将被添加到所有列表中,不管列表的类型如何。这就是我要使用通配符的原因。
编辑 #2:我更改了我的描述的语法流程并添加了一个代码片段来清除错误发生的时间以及代码编译的时间。
编辑 #3:由于评论和第一个答案试图重现我的 "bug" 并进行研究,目前的结果如下:
http://mail.openjdk.java.net/pipermail/compiler-dev/2015-June/009604.html
有人报告 Java 编译器中的错误,其中以下语句基本上是我有问题的语句:
class C1<T extends C1<T>> {}
class C2<U> extends C1<C2<U>> {}
C1<? extends C2<String>>
C1<? extends C2<?>>
没有抛出编译器错误,尽管错误报告者提到了 JLS (Java Language Specification) 的某些部分,这些部分违反了这些构造。规范中提到的部分正在处理 Bbund 交集,因此具有多个边界的泛型:
class D<T extends T1 & T2 & T3...>
(如果我理解正确的话)。 Maurizius,那个亲手研究 Java 泛型的人,回答说,确实,当时的规范还不清楚,所以构造会导致编译时错误。
我个人不明白这一点,因为我在这里看不到任何违反类型边界的行为,但我辞职了,让我的 ListBoxRenderer 看起来像这样:
public class FlatListBoxRenderer implements ComponentRenderer<GuiListBox<Object>> {
public void draw(final GuiListBox<Object> component) {
//...
}
}
我想,既然渲染器根本不关心列表内容,我可以只给出一个不特定的类型参数,然后将渲染器用于任何目的。但是现在我遇到了以下错误:
我在项目的其他地方得到了一个方法:
public <T extends GuiComponent<T>> void setComponentRenderer(final Class<T> componentClass,
final ComponentRenderer<T> renderer)
此方法将渲染器分配给 class 个 GuiElements,因此默认情况下,GuiElements 的所有实例都会获得一个渲染器。方法调用
this.setComponentRenderer(GuiListBox.class, new FlatListBoxRenderer());
失败,因为:
Inferred Type java.lang.Object for type parameter T is not within bounds; should extend [...]GuiComponent<java.lang.Object>
这个错误消息对我来说也没有意义,慢慢地我真的很困惑(顺便说一句,我知道,Java 泛型不是这种语言的一个很好的特性,许多其他语言提供更好的特性泛型,它是运行时特性,而不仅仅是代码风格特性。但那是另一回事)
我的 FlatListBoxRender DOES 确实在范围内有它的类型参数,因为类型是 GuiListBox,在我的理解中它扩展了 GuiComponent(在这个地方也不对,因为GuiComponent 确实是有边界的,但是这个边界是由 GuiListBox 的声明填充的):
public class GuiListBox<U> extends GuiComponent<GuiListBox<U>> {}
我知道我的构造非常复杂,但 Java 并非设计用于仅接受 ArrayList,它还应该处理多个 Generic。
所以,如果有人解决了我的问题,请告诉我。
另外:顺便说一句,这是我的完整项目代码:
https://github.com/Cydhra/Util/tree/master/src/main/java/de/cydhra/customgui
您可以在包 components
中找到所有 GuiComponents,在 renderer.defaults
中找到所有渲染器实现,在 renderer.theme
中找到渲染器实例
好的,我想我找到了。所以eclipse和intellij idea都弄错了泛型。 (恕我直言,这并不奇怪,因为很容易混淆)
因此 is/was 一个错误的 intellij idea 阻止它显示任何错误:
Java code that does not compile with JDK 8, but IntelliJ does not report an error
并且在 eclipse 中有 is/was 类似的错误:
Discrepancy between Eclipse compiler and javac - Enums, interfaces, and generics
希望对以后挠头的人有所帮助
我得到了一个通用 GuiComponent class:
public abstract class GuiComponent<THIS extends GuiComponent<THIS>> extends Gui {
//...
}
(如果你问自己,这个类型参数是干什么的,看
这个 class 有一堆组件子 class 是这样的:
public class ConcreteComponent extends GuiComponent<ConcreteComponent> {
//...
}
他们都有渲染器:
public interface ComponentRenderer<T extends GuiComponent<T>> {
//...
}
有具体实现:
public class FlatConcreteComponentRenderer implements ComponentRenderer<ConcreteComponent> {
//...
}
但现在我得到了以下 class:
public class GuiListBox<U> extends GuiComponent<GuiListBox<U>> {
//...
}
这本身就是通用的。这导致以下渲染器实现:
public class FlatListBoxRenderer implements ComponentRenderer<GuiListBox<?>> {
//...
}
因为渲染器不需要列表框的类型并且应该用于所有类型的列表框,所以我使用通配符,所以我不必关心类型。在渲染器的 draw 方法中,列表元素只应被视为对象并调用 toString()
。但是这个实现不起作用:
错误:(21, 73) java:类型参数 [...]components.GuiListBox 不在类型变量 T
的范围内我需要向渲染器添加一个类型,只是为了将其用于 GuiListBox
,然后编译:
public class FlatListBoxRenderer<T> implements ComponentRenderer<GuiListBox<T>>
但这不是很有用,因为默认情况下,同一个渲染器实例将被处理到所有列表框。为什么会出现这个错误,虽然我的 IDE (IntelliJ IDEA) 没有标记这个,但是构建失败?
编辑 #1:我使用 maven 编译项目,但是我的 IDE 和 maven 都无法编译 class。无论如何,这是我的 pom.xml:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.cydhra</groupId>
<artifactId>Utility</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.2</version>
</dependency>
</dependencies>
</project>
Maven 输出与我的 IDE 构建完全相同的错误。澄清一下:代码有效,如果我向渲染器添加一个类型,但这对我不起作用,因为渲染器将被添加到所有列表中,不管列表的类型如何。这就是我要使用通配符的原因。
编辑 #2:我更改了我的描述的语法流程并添加了一个代码片段来清除错误发生的时间以及代码编译的时间。
编辑 #3:由于评论和第一个答案试图重现我的 "bug" 并进行研究,目前的结果如下:
http://mail.openjdk.java.net/pipermail/compiler-dev/2015-June/009604.html
有人报告 Java 编译器中的错误,其中以下语句基本上是我有问题的语句:
class C1<T extends C1<T>> {}
class C2<U> extends C1<C2<U>> {}
C1<? extends C2<String>>
C1<? extends C2<?>>
没有抛出编译器错误,尽管错误报告者提到了 JLS (Java Language Specification) 的某些部分,这些部分违反了这些构造。规范中提到的部分正在处理 Bbund 交集,因此具有多个边界的泛型:
class D<T extends T1 & T2 & T3...>
(如果我理解正确的话)。 Maurizius,那个亲手研究 Java 泛型的人,回答说,确实,当时的规范还不清楚,所以构造会导致编译时错误。 我个人不明白这一点,因为我在这里看不到任何违反类型边界的行为,但我辞职了,让我的 ListBoxRenderer 看起来像这样:
public class FlatListBoxRenderer implements ComponentRenderer<GuiListBox<Object>> {
public void draw(final GuiListBox<Object> component) {
//...
}
}
我想,既然渲染器根本不关心列表内容,我可以只给出一个不特定的类型参数,然后将渲染器用于任何目的。但是现在我遇到了以下错误:
我在项目的其他地方得到了一个方法:
public <T extends GuiComponent<T>> void setComponentRenderer(final Class<T> componentClass,
final ComponentRenderer<T> renderer)
此方法将渲染器分配给 class 个 GuiElements,因此默认情况下,GuiElements 的所有实例都会获得一个渲染器。方法调用
this.setComponentRenderer(GuiListBox.class, new FlatListBoxRenderer());
失败,因为:
Inferred Type java.lang.Object for type parameter T is not within bounds; should extend [...]GuiComponent<java.lang.Object>
这个错误消息对我来说也没有意义,慢慢地我真的很困惑(顺便说一句,我知道,Java 泛型不是这种语言的一个很好的特性,许多其他语言提供更好的特性泛型,它是运行时特性,而不仅仅是代码风格特性。但那是另一回事)
我的 FlatListBoxRender DOES 确实在范围内有它的类型参数,因为类型是 GuiListBox,在我的理解中它扩展了 GuiComponent(在这个地方也不对,因为GuiComponent 确实是有边界的,但是这个边界是由 GuiListBox 的声明填充的):
public class GuiListBox<U> extends GuiComponent<GuiListBox<U>> {}
我知道我的构造非常复杂,但 Java 并非设计用于仅接受 ArrayList,它还应该处理多个 Generic。
所以,如果有人解决了我的问题,请告诉我。 另外:顺便说一句,这是我的完整项目代码: https://github.com/Cydhra/Util/tree/master/src/main/java/de/cydhra/customgui
您可以在包 components
中找到所有 GuiComponents,在 renderer.defaults
中找到所有渲染器实现,在 renderer.theme
好的,我想我找到了。所以eclipse和intellij idea都弄错了泛型。 (恕我直言,这并不奇怪,因为很容易混淆)
因此 is/was 一个错误的 intellij idea 阻止它显示任何错误: Java code that does not compile with JDK 8, but IntelliJ does not report an error
并且在 eclipse 中有 is/was 类似的错误: Discrepancy between Eclipse compiler and javac - Enums, interfaces, and generics
希望对以后挠头的人有所帮助