【问题标题】:Java generics: Illegal forward referenceJava 泛型:非法的前向引用
【发布时间】:2011-06-05 10:38:29
【问题描述】:

给定一个通用接口

interface Foo<A, B> { }

我想写一个实现,要求 A 是 B 的子类。所以我想做

class Bar<A, B super A> implements Foo<A, B> { }
// --> Syntax error

class Bar<A extends B, B> implements Foo<A, B> { }
// --> illegal forward reference

但似乎唯一可行的解​​决方案是:

class Bar<B, A extends B> implements Foo<A, B> { }

这有点难看,因为它颠倒了泛型参数的顺序。
这个问题有什么解决方案或变通方法吗?

【问题讨论】:

标签: java generics syntax-error


【解决方案1】:

由于这在 Java 中是不可能的,因此请尝试以不同的方式考虑 Bar&lt;B, A extends B&gt;

当您为Bar 声明一个变量时,您首先指定了父类,然后是子类。这就是Bar 的工作原理。不要把它看作是倒退——把它看作是前进。父母自然应该在孩子之前指定。您添加的这个附加关系是驱动参数顺序的原因,而不是底层接口。

【讨论】:

  • 我很感激没有办法完成它,但告诉 OP 他想错了,并暗示这可能是“[伤害他的]头”让我觉得很糟糕。 -1 是我在 SO 上见过的最没有帮助的答案。
  • @Platinum:认真的吗?我发现这里有很多很多不正确的答案,被无知的人误认为是正确的,并且被认为是最没有帮助的。无论如何,由于 OP 想要的类型参数的排序是不可能的,我认为这是可以提供的最有用的答案,因为它超出了我可能给出的简单“否”。
  • @Platinum - 我当然没有像您阅读的那样消极地阅读答案(尽管我看到最后一句话可能被错误地理解了)。他没有在哪里说 OP 正在考虑它“错误”或使用另一个可能带有负面含义的词。 “向后”的使用与 OP 使用“反向”的同义词。有时,使某些事情变得困难且不直观的原因是没有合适的“心理模型”来使用。我认为提供一种不同的方式来看待事物作为“答案”没有任何问题。
  • 它本来可以用更好的措辞...我很想看到它刚刚开始,抱歉,由于语言限制,Java 不可能。对我来说,因为大多数答案都是“换个角度思考”,所以感觉就像是在嘲弄这个问题。
  • @Platinum - 这可能只是我天生的讽刺和侵略性。我根据您的建议简化了措辞。
【解决方案2】:

看到这个问题后,我花了一点时间尝试一些我认为可能有效的不同技术。例如,构建一个通用接口ISuper&lt;B,A extends B&gt;,然后拥有Bar&lt;A,B&gt; implements ISuper&lt;B,A&gt;(以及类似的技术与子类和扩展而不是实现)但这只会导致类型错误Bar.java:1: type parameter A is not within its bound。同样,我尝试创建一个方法private &lt;A extends B&gt; Bar&lt;A,B&gt; foo() { return this; }; 并从构造函数中调用它,但这只会导致有趣的类型错误消息Bar.java:2: incompatible types found : Bar<A,B> required: Bar<A,B>

所以,很遗憾,我认为答案是否定的。显然,这不是您希望的答案,但正确的答案似乎是这是不可能的。

【讨论】:

  • 不过,我发现你的帖子很有启发性。感谢您的宝贵时间!
【解决方案3】:

已经指出,既没有解决方案,也没有很好的解决方法。这是我最终所做的。它仅适用于我的特殊情况,但如果您遇到类似问题,您可以将其作为灵感。 (这也解释了我遇到这个问题的原因)

首先有这个类(只显示相关界面):

class Pipe<Input, Output> {

    boolean hasNext();

    Input getNext();

    void setNext(Output o);

}

Foo接口其实是

interface Processor<Input, Output> {

    process(Pipe<Input, Output> p);

}

Bar 的类应该像这样工作

class JustCopyIt<Input, Output> implements Processor<Input, Output> {

    process(Pipe<Input, Output> p) {
       while (p.hasNext()) p.setNext(p.getNext());
    }

}

最简单的方法是像这样转换值:p.setNext((Output) p.getNext())。 但这很糟糕,因为它允许创建JustCopyIt&lt;Integer, String&gt; 的实例。调用这个对象会在某些时候神秘地失败,但不会在实际错误发生的时候失败。

在这里执行class JustCopyIt&lt;Type&gt; implements Processor&lt;Type, Type&gt; 也行不通,因为那样我就无法处理Pipe&lt;String, Object&gt;

所以我最后做的就是把界面改成这样:

interface Processor<Input, Output> {

    process(Pipe<? extends Input, ? super Output> p);

}

这样,JustCopyIt&lt;List&gt; 能够处理Pipe&lt;ArrayList, Collection&gt;

虽然这在技术上似乎是唯一有效的解决方案,但它仍然很糟糕,因为它 1) 仅适用于这种特殊情况,2) 需要我更改接口(这并不总是可能的)和 3) 编写了代码其他处理器丑陋。

编辑:
再次阅读 Keiths 的回答启发了我另一种解决方案:

public abstract class Bar<A, B> implements Foo<A, B> {

    public static <B, A extends B> Bar<A, B> newInstance() {
        return new BarImpl<B, A>();
    }

    private static class BarImpl<B, A extends B> extends Bar<A, B> {
        // code goes here
    }

}

// clean code without visible reversed parameters
Bar<Integer, Object> bar1 = Bar.newInstance();
Bar<Object, Integer> bar2 = Bar.newInstance(); // <- compile error

【讨论】:

  • 我喜欢这样。这是一个巧妙的解决方法。我确信在某些时候其他人会阅读该代码并发现它令人困惑,但从外部使用时,这是一个明确的改进。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多