【问题标题】:Java Generic Wildcard breaks type boundsJava 通用通配符打破了类型界限
【发布时间】:2016-06-29 23:05:56
【问题描述】:

我有一个通用 GuiComponent 类:

public abstract class GuiComponent<THIS extends GuiComponent<THIS>> extends Gui {
    //...
}

(如果你问自己,这个类型参数是干什么的,看Calling a Generic Interface Method does not work

这个类有一堆这样的组件子类:

public class ConcreteComponent extends GuiComponent<ConcreteComponent> {
    //...
}

他们都有渲染器:

public interface ComponentRenderer<T extends GuiComponent<T>> {
    //...
}

有具体的实现:

public class FlatConcreteComponentRenderer implements ComponentRenderer<ConcreteComponent> {
    //...
}

但现在我得到了以下课程:

public class GuiListBox<U> extends GuiComponent<GuiListBox<U>> {
    //...
}

这本身就是通用的。这导致以下渲染器实现:

public class FlatListBoxRenderer implements ComponentRenderer<GuiListBox<?>> {
    //...
}

因为渲染器不需要列表框的类型并且应该用于所有类型的列表框,所以我使用通配符,所以我不必关心类型。在渲染器的 draw 方法中,列表元素只应被视为对象并调用toString()。但是这个实现不起作用:

Error:(21, 73) java: type argument [...]components.GuiListBox 不在类型变量 T 的范围内

我需要给渲染器添加一个类型,只为GuiListBox使用,然后编译:

public class FlatListBoxRenderer<T> implements ComponentRenderer<GuiListBox<T>>

但这不是很有用,因为默认情况下,所有 ListBox 都应处理相同的渲染器实例。为什么会出现这个错误,虽然我的 IDE (IntelliJ IDEA) 没有标记这个,但是构建失败?

编辑 #1:我使用 maven 编译项目,但我的 IDE 和 maven 都无法编译该类。无论如何,这是我的 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:我改变了我的描述的语法流程,并添加了一个代码sn-p来清除错误发生的时间,以及代码编译的时间。

编辑#3:由于 cmets 和第一个答案试图重现并研究我的“错误”,所以到目前为止的结果如下:

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

(如果我理解正确的话)。掌握 Java 泛型的人 Maurizius 回答说,确实,当时的规范不清楚,因此构造导致编译时错误。 我个人不明白这一点,因为我在这里看不到任何违反类型边界的行为,但我辞职并让我的 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)

此方法将一个 Renderer 分配给 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中找到渲染器的实例

【问题讨论】:

  • 这个不清楚。如果此代码有效,那么哪些代码不适合您?
  • 请同时发布您的组件渲染器使用这些列表框的代码
  • 代码不起作用。它不编译。渲染器中还没有使用列表框的代码。它根本无法编译。
  • 这很奇怪。我尝试了您的代码,它为我编译,在 Java 8 中没有任何警告或错误。
  • 我尝试用 maven 编译它:Source and Target 1.8 - 同样的错误。我的 IDE 是 IntelliJ IDEA,Java 版本 1.8_51。 IDEA 没有显示语法错误或警告,我不明白为什么与 T 的界限无关的通配符不应该工作......

标签: java generics wildcard


【解决方案1】:

好的,我想我找到了。所以 eclipse 和 intellij 的想法都把泛型弄错了。 (恕我直言,这并不奇怪,因为它很容易混淆)

所以有/曾经有一个错误 intellij 想法阻止它显示任何错误: Java code that does not compile with JDK 8, but IntelliJ does not report an error

并且在 eclipse 中有/曾经有过类似的错误: Discrepancy between Eclipse compiler and javac - Enums, interfaces, and generics

希望对以后会摸不着头脑的人有所帮助。

【讨论】:

  • 是的,这正是错误所在。但是有谁知道为什么会发生这个错误,尽管代码是有意义的,任何人都可以给出提示,如何修复、绕过或解决这个问题?
  • 好的,因为我研究了很多关于你的链接和讨论,看来,我曾经搜索过的这个构造能够编译,但在某些方面与规范不同,所以它是固定和禁止。我个人不明白为什么要这样做,因为我看不出哪里有问题,对我来说,这不是理论上的构造,但我确实想实现和使用那些现在被禁用的功能。悲伤的故事...
  • @Cydhra 这有时会发生。至少你知道是什么导致了这种奇怪的行为。如果您认为这回答了您的问题“为什么会发生此错误,尽管我的 IDE (IntelliJ IDEA) 没有标记这一点,但构建失败?”那么请将其标记为已接受。编码愉快!
猜你喜欢
  • 1970-01-01
  • 2015-08-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多