2

我有一个(通用)类,其中包含其他类的元数据。元数据以多种方式使用(写入和读取 XML 数据、数据库、作为文本输出等)。到目前为止,这有效。但是在将所有这些用于从其他类继承的类时,我遇到了一个问题。

请查看以下代码(我尝试生成一个可编译的最小示例,但下面标记的行除外):

class A {
  public Meta<? extends A> getMeta() {
    return new Meta<A>();
  }

  public void output() {
    /*
     * Error shown in eclipse for the next line:
     * The method output(capture#1-of ? extends A) in the type
     * Outputter<capture#1-of ? extends A> is not applicable for the arguments
     * (A)
     */
    getMeta().getOutputter().output(this);
  }
}

class B extends A {
  @Override
  public Meta<? extends B> getMeta() {
    return new Meta<B>();
  }
}

class Meta<CLS> {
  public Outputter<CLS> getOutputter() {
    return null;
  }
}

class Outputter<CLS> {
  public void output(CLS obj) {
  }
}

我可以更改A.getMeta()为返回Meta<A>以使上面的行编译,但是我不能像Meta<B> getMeta()在 B 类中那样覆盖它。

关于如何解决这个问题的任何想法?

4

3 回答 3

1

您无法覆盖的原因public Meta<A> getMeta()public Meta<B> getMeta()B 的实例可以转换为 A,并且这样的转换实例需要返回 a Meta<A>。虽然 aMeta<B>可以用作 a Meta<A>,但编译器并不知道这一点。

想象一下,您正在返回List<A>并且List<B>. 允许将 A 和 B 的实例放入 aList<B>中,但不允许将 B 的实例放入 aList<A>中,因此List<B>B 实际返回的那个不能用作 a List<A>

更改List<A>List<? extends A>允许代码编译,因为List<B>从技术上讲是 的子类List<? extends A>,但它不允许您执行您可能期望的所有操作。

B b = new B();
A casted = (A)b;
casted.getList().add(new A());

编译器将毫无问题地接受第一行和第二行,但第三行会出现问题:

The method add(capture#1-of ? extends A) in the type List<capture#1-of ? extends A> is not applicable for the arguments (A)

如果你稍微调查一下,你会发现这个强制转换的变量既不会接受 A 也不会接受 B 的元素。编译器已经记住该对象是强制转换的,并且实际上可能无法接受任何扩展 A 的东西。

我正在尝试寻找有关此行为的文档,但我失败了。Eclipse 工具提示建议我应该给它一个 type 元素null,这显然是无稽之谈。如果我在上面找到任何东西,我会更新。


编辑:所描述的行为是此处描述的“捕获转换”的产物。Capture Conversion 允许通配符通过在赋值和强制转换过程中更改类型参数的范围来更有用。在我们的代码中发生的只是边界被限制为null类型。

于 2013-07-25T08:51:23.243 回答
1

如果你这样做呢?它还需要一堂课,但似乎它会起作用:

class T{
 //put common methods here, generic methods are not common, so they will not be here
}

 class A extends T{
  public Meta<A> getMeta() {
    return new Meta<A>();
  }

  public void output() {
    /*
     * Error shown in eclipse for the next line:
     * The method output(capture#1-of ? extends A) in the type
     * Outputter<capture#1-of ? extends A> is not applicable for the arguments
     * (A)
     */
    getMeta().getOutputter().output(this);
  }
}

class B extends T {

  public Meta<B> getMeta() {
    return new Meta<B>();
  }
}

class Meta<CLS> {
  public Outputter<CLS> getOutputter() {
    return null;
  }
}

class Outputter<CLS> {
  public void output(CLS obj) {
  }
}

如果您不想创建其他方法,则可以使用复合。关于继承的组合有很多很好的讨论。

A除了和B类之外,一切都将相同:

  class A{
      public Meta<A> getMeta() {
        return new Meta<A>();
      }
      ...
     } 

    class B {

      private class A a; 

      public Meta<B> getMeta() {
        return new Meta<B>();
      }
      //use a here if you need, a is composed into B
    }
于 2013-07-25T08:28:26.883 回答
0

I will answer this myself since I found a working solution.

Although this solution is not type-safe, it works and requires the least changes to my existing codebase. If anyone comes up with something that works and doesn't require the @SuppressWarnings, I will accept that answer.

class A {
  Meta<?> getMeta() {
    return new Meta<A>();
  }

  @SuppressWarnings({ "rawtypes", "unchecked" })
  public void output() {
    Outputter out = getMeta().getOutputter();
    out.output(this);
  }
}

class B extends A {
  @Override
  public Meta<?> getMeta() {
    return new Meta<B>();
  }
}

class Meta<CLS> {
  public Outputter<CLS> getOutputter() {
    return null;
  }
}

class Outputter<CLS> {
  public void output(CLS obj) {
  }
}
于 2013-07-25T13:16:56.207 回答