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()上記の行をコンパイルするために戻るように変更できますが、クラス B のMeta<A>ようにオーバーライドすることはできません。Meta<B> getMeta()

これを解決する方法についてのアイデアはありますか?

4

3 に答える 3

1

public Meta<A> getMeta()でオーバーライドできない理由public Meta<B> getMeta()は、 B のインスタンスが A にキャスト可能であり、そのようなキャストされたインスタンスは を返す必要があるためMeta<A>です。Meta<B>aは a として機能する可能性がありますがMeta<A>、コンパイラはそれを知りません。

代わりに、戻っList<A>てきてList<B>. A と B のインスタンスを aに入れることは許されますが、 B のインスタンスを a に入れることは許されList<B>ません。List<A>List<B>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());

コンパイラは問題なく 1 行目と 2 行目を受け入れますが、3 行目で問題が発生します。

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、これは明らかにナンセンスです。何か見つけたら更新します。


編集:説明されている動作は、ここで説明されている「キャプチャ変換」の製品です。キャプチャ変換を使用すると、割り当てとキャストの過程で型引数の境界を変更することで、ワイルドカードをより便利に使用できます。私たちのコードで起こっていることは、境界がnull型に制限されているということです。

于 2013-07-25T08:51:23.243 に答える
1

これをするとどうなりますか?もう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

実用的な解決策を見つけたので、私はこれに自分で答えます。

このソリューションはタイプセーフではありませんが、機能し、既存のコードベースへの変更は最小限で済みます。誰かが機能し、を必要としない何かを思いついた場合@SuppressWarnings、私はその答えを受け入れます。

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 に答える