私は Java ジェネリックをかなりよく理解していると思っていましたが、java.lang.Enum に次のような記述がありました。
class Enum<E extends Enum<E>>
誰かがこの型パラメータを解釈する方法を説明できますか? 同様の型パラメーターを使用できる場所の他の例を提供するためのボーナスポイント。
これは、列挙型の型引数が、それ自体が同じ型引数を持つ列挙型から派生する必要があることを意味します。これはどのように起こりますか?型引数を新しい型自体にすることによって。したがって、StatusCode という列挙型を取得した場合、次のようになります。
public class StatusCode extends Enum<StatusCode>
制約を確認すると、-so が得Enum<StatusCode>
られE=StatusCode
ます。確認しましょう:E
拡張しEnum<StatusCode>
ますか? はい!大丈夫です。
あなたは、これが何を意味するのか自問するかもしれません:) まあ、それは、Enum の API がそれ自体を参照できることを意味します - たとえば、それが をEnum<E>
実装していると言うことができますComparable<E>
。基本クラスは (列挙型の場合) 比較を行うことができますが、適切な種類の列挙型のみを相互に比較することを確認できます。(編集: まあ、ほぼ - 下部の編集を参照してください。)
ProtocolBuffers の C# ポートで同様のものを使用しました。「メッセージ」(不変) と「ビルダー」(メッセージを作成するために使用される可変) があり、それらは型のペアとして提供されます。関連するインターフェースは次のとおりです。
public interface IBuilder<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>
public interface IMessage<TMessage, TBuilder>
where TMessage : IMessage<TMessage, TBuilder>
where TBuilder : IBuilder<TMessage, TBuilder>
つまり、メッセージから適切なビルダーを取得でき (たとえば、メッセージのコピーを取得して一部を変更する)、ビルダーから、ビルドが完了したときに適切なメッセージを取得できます。API のユーザーが実際にこれを気にする必要がないのは良い仕事です。これは恐ろしく複雑であり、そこにたどり着くまでに数回の反復が必要でした。
編集:これは、それ自体は問題ないが、同じ型ではない型引数を使用する奇妙な型を作成することを妨げるものではないことに注意してください。目的は、間違ったケースからあなたを保護するのではなく、正しいケースで利益を与えることです.
Enum
とにかくJavaで「特別に」処理されなかった場合は、(コメントに記載されているように)次のタイプを作成できます。
public class First extends Enum<First> {}
public class Second extends Enum<First> {}
Second
...Comparable<First>
ではなく実装しますが、それ自体は問題ありません。Comparable<Second>
First
以下は、本Java Generics and Collectionsの説明の修正版です。Enum
enum Season { WINTER, SPRING, SUMMER, FALL }
クラスに展開されます
final class Season extends ...
ここで...
、列挙型の何らかのパラメータ化された基本クラスになります。それが何であるべきかを考えてみましょう。の要件の 1 つは、Season
実装する必要があることComparable<Season>
です。だから私たちは必要になるでしょう
Season extends ... implements Comparable<Season>
これを機能させるために何を使用できます...
か?のパラメータ化でなければならないことを考えるとEnum
、唯一の選択肢はEnum<Season>
であるため、次のことができます。
Season extends Enum<Season>
Enum<Season> implements Comparable<Season>
のような型Enum
でパラメータ化されていますSeason
。から抽象化するSeason
と、のパラメーターがEnum
条件を満たす任意の型であることがわかります
E extends Enum<E>
Maurice Naftalin (共著、Java Generics and Collections)
これは、簡単な例と、サブクラスの連鎖メソッド呼び出しを実装するために使用できる手法によって説明できます。以下の例では、 をsetName
返すNode
ため、チェーンは に対して機能しませんCity
:
class Node {
String name;
Node setName(String name) {
this.name = name;
return this;
}
}
class City extends Node {
int square;
City setSquare(int square) {
this.square = square;
return this;
}
}
public static void main(String[] args) {
City city = new City()
.setName("LA")
.setSquare(100); // won't compile, setName() returns Node
}
したがって、ジェネリック宣言でサブクラスを参照してCity
、正しい型を返すようにすることができます。
abstract class Node<SELF extends Node<SELF>>{
String name;
SELF setName(String name) {
this.name = name;
return self();
}
protected abstract SELF self();
}
class City extends Node<City> {
int square;
City setSquare(int square) {
this.square = square;
return self();
}
@Override
protected City self() {
return this;
}
public static void main(String[] args) {
City city = new City()
.setName("LA")
.setSquare(100); // ok!
}
}
それが何を意味するのか疑問に思っているのはあなただけではありません。Chaotic Java ブログを参照してください。
「クラスがこのクラスを拡張する場合、パラメーター E を渡す必要があります。パラメーター E の境界は、同じパラメーター E でこのクラスを拡張するクラス用です」。
この投稿は、これらの「再帰的ジェネリック型」の問題を完全に明確にしました。この特定の構造が必要な別のケースを追加したかっただけです。
ジェネリック グラフにジェネリック ノードがあるとします。
public abstract class Node<T extends Node<T>>
{
public void addNeighbor(T);
public void addNeighbors(Collection<? extends T> nodes);
public Collection<T> getNeighbor();
}
次に、特殊なタイプのグラフを作成できます。
public class City extends Node<City>
{
public void addNeighbor(City){...}
public void addNeighbors(Collection<? extends City> nodes){...}
public Collection<City> getNeighbor(){...}
}