4

私は流暢な API に取り組んでおり、Java のジェネリック メソッドを利用して、ユーザーの型変換を処理するエレガントな API を提供しようとしています。ただし、型の消去が原因で、動作させるのに問題が発生しています。

これは、私が直面している問題を示すインターフェースの簡略版です。

interface Query<T extends Query<T>> {
  T execute();
  Query<T> appendClause();
}

interface A<T extends A> extends Query<T> { }

class AImpl implements A<A> {
  A execute() { ... }
  Query<A> appendClause() { ... }
}

でエラーが発生しAImpl.appendClause()ます。コンパイラは、それAが範囲内にないため、クエリを拡張する必要があると言います。私が知る限り、AImpl実装する私の宣言拡張A<A>することを意味します。A Query<A>

ここで別の回答に続いて、次のように変更して、解決できない可能性のある再帰を分割しようとしましAImplた。

class AImpl implements A<AImpl> {
  AImpl execute() { ... }
  Query<AImpl> appendClause() { ... }
}

A現在、 「型パラメーター T がその境界内にありません」というコンパイル エラーが発生しています。

これを処理する方法について誰か提案がありますか? Java のジェネリックが頭を悩ませています。

編集

A の定義を次のように変更しました。

interface A<T extends A<T>> extends Query<T> { }

これで、AImpl の 2 番目の実装が機能しました。しかし、A のサブクラスを照会できるように API を拡張したいとも考えています。

interface B<T extends B> extends A<B> { }

class BImpl implements B<BImpl> {
  BImpl execute() { ... }
  Query<BImpl> appendClause() { ... }
}

この定義により、Bの宣言でエラーが発生します。「型パラメーター B はその境界内にありません。A を拡張する必要があります」。

Bを次のように変更することで、そのエラーを解決できます

interface B<T extends B<T>> extends A<B<T>> { }

しかし今、私のインターフェース定義はばかげているように見え始めており、何か間違ったことをしているような気がします. さらに、BImpl でまだエラーが発生します。

サブクラス定義をクリーンアップして、継承階層全体を指定する必要がないようにするextends方法、または BImpl を機能させる方法について何か提案はありますか?

編集2

さて、私は別の問題に遭遇しました。クエリを生成するファクトリ クラスがあります。

public class QueryFactory {
  public static <T extends Query<T>> Query<T> queryForType(Class<T> type) { ... }
}

およびクライアント コード:

Query<B> bQuery = QueryFactory.queryForType(B.class);

私のクライアント コードは、bQuery の宣言でエラーを表示しています。この時点で、Bはクエリを拡張したと思いました...

queryForType() 呼び出しを変更すると、このエラーはなくなります

Query<? extends B> bQuery = QueryFactory.queryForType(B.class);

しかし、コンパイラからまだチェックされていない警告が表示されます。

unchecked method invocation: <T>queryForType(Class<T>) in QueryFactory is applied to Class<B>
unchecked conversion found: Query required: Query<B>

型消去が再び私と戦っているように見えますが、これらの警告がわかりません。私を軌道に乗せるための提案は他にありますか?ありがとう!

編集3

クライアントコードを次のように変更すると、警告なしでコンパイルできます

Query<BImpl> bQuery = QueryFactory.queryForType(BImpl.class);

しかし、実装クラスを API のユーザーから隠したいと思っています。問題がそれと関係がある場合に備えて、B をインターフェイスではなく抽象クラスにしようとしましたが、役に立ちませんでした。

4

1 に答える 1

6

ここで、あなたのインターフェースは生の型を拡張するA型パラメータを宣言します。試してみてくださいTA

//                      v--- Add this
interface A<T extends A<T>> extends Query<T> { }

これにより、ジェネリック化された withが確実にT拡張されます。このようにして、インターフェイスで指定された範囲内になります。ATTQuery

AImplこれは、その implementsの 2 番目のバージョンで動作しますA<AImpl>

編集

言わなければならない

interface B<T extends B<T>> extends A<B<T>> { }

非常に複雑に見えます。Bインターフェースについては、から拡張したのとA同じようにから拡張AしますQuery

//                                    v-- Don't mention B here
interface B<T extends B<T>> extends A<T> { }

次に、BImplクラスはクラスに似たものになりますAImpl

class BImpl implements B<BImpl> {
    public BImpl execute() { ... }
    public Query<BImpl> appendClause() { ... }
}
于 2013-05-14T21:43:39.863 に答える