10

これは、Java でよく知られているイディオムです。たとえば、この SO ディスカッションを参照してください。したがって、基本的に、それを実装するクラスがそのクラスのオブジェクトと比較するメソッドを持つ必要があるインターフェイスを定義するには、次のようにします。

public interface IComparable<T extends IComparable<T>> {
    public int compare(T item);
}

(注: 特定のユース ケースを解決するには、明らかに不必要に複雑な方法です。この投稿を参照してください。ただし、特定のアプリケーションは気にせずに、再帰構文を解釈する方法を調べています)。

要するに、これは再帰に明らかな終わりのない再帰的な定義であり、コンパイラ/型システムがこれをどのようにやってのけるのかわかりません。

また、言葉で表現しようとすると (「IComparable は、インスタンスが IComparable インターフェイスを実装するクラスのオブジェクトと比較できるクラスです」)、非論理的な循環定義が生成されます。これは、哲学/論理/数学では使用できませんが、明らかにコンパイラ設計で実行可能 (!)。

さらに、元の慣用句の構文が受け入れられる場合は、次のように言うことも許されるようです。

public interface IComparable<T extends IComparable<T extends IComparable<T>>> {
    public int compare(T item);
}

...しかし、コンパイラは、明らかに1つのレベルのみを許可することに躊躇しますextend

この種の再帰的なジェネリック定義を理解するのを助けるために、誰かが光を当てることができますか?

アップデート

受け入れられた回答(BartoszKP)に基づいて、私が今理解していると思うのは次のとおりです。

「再帰的」定義は次のように読まれるべきではありません (「定義が依存する」関係を表す矢印)。

[ IComparable<T> ] -------> [ IComparable<T> ]

...これは非論理的(循環的)ですが、次のようになります。

[ IComparable<T> ] ----------> [ list of methods (parameterized by T) ]
          \                                      ^
           \                                     |
            \-------> [ type bound on T ]-------/

...これは非論理的ではないため、コンパイラで実行できます。つまり、 の定義は、IComparableそれが定義するメソッドのリストと、それが定義するタイプの境界によって与えられT、後者もメソッドのリストに依存します。したがって、再帰はありません。

4

3 に答える 3

5

これは、Java でよく知られているイディオムです。

いいえ。役に立たないので、使用しないでください。Enum(型はコンパイラによって生成されるため、特殊なケースです) を除いてenum、このパターンは、公式の Java ライブラリや公式の Java ドキュメントのどこにも見られません。

型変数のジェネリック境界の目的は、関係を設定して、ジェネリック クラスまたはジェネリック メソッド内のコードがその保証を使用して、潜在的に安全ではないキャストなしで一部の操作を実行できるようにすることです。ジェネリック境界は、コード内のキャストを防止しながら、可能な限り制限を緩和する必要があります。

したがって、バウンドの唯一の正当な目的は、interface IComparable<T extends IComparable<T>>1) 実際にはインターフェイスではなくクラスであり (インターフェイスにはコードがないため、避けるべきキャストはありません)、2) クラス内のコードが以下を実行する必要がある場合です。次の操作: メソッドを使用してTそれ自体から抜け出し、その上で を必要とするメソッドを呼び出しますIComparable<T>。次に、Textendsの保証IComparable<T>により、キャストなしでこれを行うことができます。

このパターンの多くの使用例を見てきましたが、99% のケースで、上記の操作を行う必要はありません。むしろ、99% の確率で、人々がやりたいことは、上記のパターンでは保証されない (そして Java では表現できない) T"と等しい" を何らかの方法で表現することです。拡張IComparable<T>することを保証するだけで、T拡張することは保証しIComparable<T>ません。IComparable<T>T

ほとんどの場合、人々がこのパターンを使用するときは、内部で行うクラスに使用します(T)this。キャストが必要であるという事実は、これが潜在的に安全でないことを示しています。その型の安全性は境界によって保証されていませんthis((型のIComparable<T>) extendsの保証はありませんT)。実際には安全ではありません。よく知られている例はclass Foo extends IComparable<Foo>、 でありclass Bar extends IComparable<Foo>、したがってthis( 型の) は( )Barを拡張しません。TFoo

したがって、そのコードを見たことがあれば、それが何をするかについて何らかの誤解を持って書かれていることはほぼ確実であり、ほとんどの場合、次のように変更する必要があります。

public interface IComparable<T> {
    public int compare(T item);
}

interface IComparable<T extends IComparable<T>>演習として、機能するスニペットと機能interface IComparable<T>しないスニペットを見つけてください。

于 2013-09-01T08:16:02.727 に答える
3

実際、あなたのケースでは再帰的な定義は必要ありません。

public interface IComparable<T> {
  public int compare(T item);
}

public class Foo implements IComparable<Foo> {
  @Override
  public int compare(Foo o) {
    return 0;
  }
}

あなたが何をしようとしているのかを定義するのに十分です。

再帰的な定義の一般的な場所は、 を使用している場合ですenum。このコンテキストでは、クラスが列挙するジェネリック型で定義されているE extends Enum<E>ため、実際には非常に理にかなっています。EnumE

参照された議論に関する@NPEの投稿への物語として:

public interface ResultItem<T extends ResultItem<T>> {
    public int getConfidence();
    public boolean equals(T item);
    public T cloneWithConfidence(int newConfidence);
}

ここで起こっていることは、特定のジェネリック クラスに関してクラスを定義する代わりに、拡張する必要がある、つまり のサブクラスであるTと述べているということです。もちろん、はジェネリック クラスであるため、この場合はそれ自体であるジェネリック パラメータを指定する必要があります。したがって、. これは再帰的な定義ではなく、サブクラスのキャプチャ定義です。T ResultItemResultItemResultItemTT extends ResultItem<T>

于 2013-08-31T23:02:15.687 に答える
2

まず、再発に対する疑問について:

それはある種の再発ですが (X は X に関連する何かによって定義されているため)、たとえそうであったとしても、それは 1 つのレベルにすぎません。defineコンパイラが、ソース ファイルを読み取ってコンパイルすることによってまだ認識していないものを定義する関数と、ユーティリティ関数を持っていると想像してくださいget_list_of_methods。したがって、の場合は次のようになりResultItemます。

1)define(interface ResultItem<T extends ResultItem<T>>)

このためには、インナーの定義ResultItem<T>が必要です。しかし、 についてすべてを知る必要はありません。ResultItem<T>何かが拡張することの意味だけを知りたいResultItem<T>ので、最初は次のようになります。

2)get_list_of_methods(ResultItem<T>)

これにより、ソース ファイルが解析され、すべてのメソッドのリストが取得ResultItem<T>されます。今はコンパイルする必要はありません。

T3)のすべてのメソッドを持つ一般的な制約ガーディングを作成します。ResultItem<T>

4) ステップ 1) の続き - ソース ファイルを解析して、 のすべてのメソッドの定義を取得しますResultItem<T>。ステップ 2でどのメソッドが実装されているかがわかればT、メソッドをコンパイルできます。

おそらく実際にはこれはもっと複雑ですが、これを解釈するために繰り返しが必要ないことを確認するのに役立ちます. class Xしたがって、 を受け入れるメソッドを持つよりも、繰り返し発生することはありませんX。複数のパスが必要です。古い言語では、そのようなlookahead定義は不可能です。C++ でも前方宣言が必要になりました。

class X;

class Y
{
private:
  X* x;
};

class X
{
};

次に、これの使い勝手について。コメントや関連トピックから情報を収集すると、このメカニズムは、特定のインターフェイスを実装するクラスに関する意図を表現し、インターフェイスをより便利で便利にするために使用されると述べたいと思います。このトピックで詳しく説明しています:インターフェイス インスタンス メソッドが同じクラスの引数のみを受け入れるようにするにはどうすればよいですか? .

2 番目のケースでは、インターフェイスを実装するクラスの利便性は向上しません。

public interface IComparable<T extends IComparable<T>> {
  public int compare(T item);
}

しかし、それは、ある実体がcomparable同種の比較可能な項目とのみ比較可能であるという事実を表現していると解釈することができます。class X implements IComparable<Y>Y も同等である限り、他の回答やコメントに記載されているように、これは最良の例ではありません。詳細はこちら:インターフェイス インスタンス メソッドが同じクラスの引数のみを受け入れるようにするにはどうすればよいですか? .

このイディオムの使用に関する詳細: http://www.angelikalanger.com/GenericsFAQ/FAQSections/TypeParameters.html#FAQ106 .

于 2013-09-01T11:23:53.553 に答える