【问题标题】:Calling a Generic Interface Method does not work调用通用接口方法不起作用
【发布时间】:2016-06-07 13:49:36
【问题描述】:

我得到了一个泛型接口,其中一个方法接受泛型类型的参数:

public interface ComponentRenderer<T extends GuiComponent> {
    public void draw(T component);
}

此外,我有一个抽象类,它使用有界通配符声明此接口类型的变量:

public abstract class GuiComponent extends Gui {
    private ComponentRenderer<? extends GuiComponent> componentRenderer;

    public void draw() {
        this.componentRenderer.draw(this);
    }

    //and a setter and getter for the ComponentRenderer
}

还有一个子类,它为 componentRenderer 设置了一个实现:

public class GuiButton extends GuiComponent {
    public GuiButton(/* ... */) {
        //...
        this.setComponentRenderer(new FlatButtonRenderer());
    }

FlatButtonRenderer 的实现方式为:

public class FlatButtonRenderer implements ComponentRenderer<GuiButton> {

    @Override
    public void draw(final GuiButton component) {
        //...
    }
}

我看不到哪里出错了,但是 GuiComponent 中的调用 componentRenderer.draw(this) 无法正常工作,出现以下错误:

据我了解,它告诉我,我不能使用 GuiComponent,因为它不是从 GuiComponent 派生的,这没有任何意义。我也试过? super GuiComponent,会接受draw()的调用,但是不接受FlatButtonRenderer的实现

我不明白这个语法错误,有没有人知道,我需要如何更改代码?

编辑: 当我在调用 draw() 时使用 IDE 的代码完成时,它告诉我,draw 接受一个“null”类型的参数,所以由于某种原因,它无法弄清楚参数应该是哪种类型。 ..

【问题讨论】:

  • 与您的问题无关:您为什么要尝试将绘图拉入不同的对象?一个 GuiComponent 的目的不就是要在屏幕上绘制吗?
  • 因为我希望 Gui Button 包含按钮逻辑,这总是相同的,但我希望按钮可以有不同的布局和样式。所以可以切换渲染器,我得到了一个布局不同的工作按钮。
  • 很公平。我不知道您的用例的详细信息,但从我所见,定义样式或子类化使组件更易于使用。

标签: java generics interface


【解决方案1】:

问题在于? extends GuiComponent 表示“GuiComponent 的一个特定子类型,但不知道是哪个”。

编译器不知道thisComponentRenderer 的正确GuiComponent 子类型。可能是渲染器只能与其他一些特定的子类一起使用。

您必须使用某种自类型模式以类型安全的方式执行此操作。这样你就可以将渲染器的类型变量与GuiComponent 子类的类型“连接”起来。

例子:

class Gui {}

interface ComponentRenderer<T extends GuiComponent<T>> {
    public void draw(T component);
}

// T is the self-type. Subclasses will set it to their own type. In this way this class
// can refer to the type of its subclasses.
abstract class GuiComponent<T extends GuiComponent<T>> extends Gui {
    private ComponentRenderer<T> componentRenderer;

    public void draw() {
        this.componentRenderer.draw(thisSub());
    }

    public void setComponentRenderer(ComponentRenderer<T> r) {}

    // This method is needed for the superclass to be able to use 'this'
    // with a subclass type. Sub-classes must override it to return 'this'
    public abstract T thisSub();

    //and a setter and getter for the ComponentRenderer
}

// Here the self-type parameter is set
class GuiButton extends GuiComponent<GuiButton> {
    public GuiButton(/* ... */) {
        //...
        this.setComponentRenderer(new FlatButtonRenderer());
    }

    class FlatButtonRenderer implements ComponentRenderer<GuiButton> {
        @Override
        public void draw(final GuiButton component) {
            //...
        }
    }

    @Override
    public GuiButton thisSub() {
        return this;
    }
}

这最初(我认为)称为curiously recurring template patternThis answer 解释更多。

【讨论】:

  • 这看起来很不健康,也不太合乎逻辑(我的意思是:class GuiComponent>;这有什么意义?)但它确实有效。谢谢 :D 但不要介意我的强制性评论:Java 泛型不仅仅用于泛型列表。
  • @Cydhra:当你第一次看到它时,它看起来真的很奇怪,但当你看到它时,它实际上很有意义。 T 只是超类的当前子类的类型。我将在答案中添加一些包含更多信息的链接。
  • @Cydhra:但是必须一直写出所有这些自我类型非常麻烦。在您的情况下,也许更好的解决方案是放弃类型安全,从ComponentRenderer 中删除类型参数,只需将参数转换为ComponentRenderer.draw 内的正确子组件,并手动确保将正确类型的组件发送到正确的渲染器。
  • Mh 没有。我讨厌 TypeCasting,您的解决方案可能看起来有点混乱,但 TypeCasting 只是糟糕的代码风格。你应该做的唯一类型转换是当你覆盖 equals()
  • @Cydhra:你的问题让我想起了this old answer。如果您喜欢使用高级泛型来获得类型安全,那么您可能会发现它很有趣。那个人使用自我型模式三次!
【解决方案2】:

在 GuiComponent 中,将您的 componentRenderer 声明更改为:

ComponentRenderer<GuiComponent> componentRenderer;

【讨论】:

  • 这行不通,因为现在我不能再将组件渲染器设置为 FlatButtonRenderer,因为这个实现使用 GuiButton 作为类型而不是 GuiComponent
  • 我有这个代码编译没有错误(在创建一个模拟 Gui 类之后)。具体来说,进行此更改后会出现什么错误?
  • GuiButton中setComponentRenderer调用出错,FlatButtonRenderer不适用于setComponentRenderer(ComponentRenderer)方法
  • 这应该如何工作?组件渲染器 != ComponentRenderer;所以我不能使用这个设置器。 setter 仅在 componentRenderer 和 setter 参数属于同一类型时才有效。
  • 对不起。最后一次编辑是不正确的建议。我正在研究这个,但还没有找到解决方案。问题来自 GuiButton 尝试使用 ComponentRenderer 分配 ComponentRenderer。我不确定您要完成什么,但循环继承结构使问题复杂化。特别是 ComponentRenderer 和 GuiComponent 都相互引用。简化这种结构可能会解决一些问题。
猜你喜欢
  • 2021-09-29
  • 1970-01-01
  • 2018-12-07
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-04-03
  • 2017-06-19
  • 1970-01-01
相关资源
最近更新 更多