Javaジェネリックをかなりよく理解していると思いましたが、Java.lang.Enumで次のことに遭遇しました。
class Enum<E extends Enum<E>>
誰かがこの型パラメータを解釈する方法を説明できますか?同様のタイプのパラメーターを使用できる他の例を提供するためのボーナスポイント。
これは、enumの型引数が、それ自体が同じ型引数を持つ列挙型から派生する必要があることを意味します。これはどのように起こりますか?型引数を新しい型自体にすることにより。したがって、StatusCodeという列挙型を取得した場合、次と同等になります。
public class StatusCode extends Enum<StatusCode>
制約を確認すると、Enum<StatusCode>
が得られているので、E=StatusCode
です。確認しましょう:E
はEnum<StatusCode>
を拡張しますか?はい!大丈夫です.
これのポイントが何であるかを自問するかもしれません:)まあ、それはEnumのAPIがそれ自身を参照できることを意味します-例えば、Enum<E>
はComparable<E>
を実装すると言うことができます。基本クラスは比較を行うことができます(enumの場合)が、適切な種類のenumのみを比較することを確認できます。 (編集:まあ、ほぼ-下部の編集を参照してください。)
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 ...
...
は、何らかの方法でパラメーター化されたEnumの基本クラスです。それがどうあるべきかを考えてみましょう。 Season
の要件の1つは、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>
モーリス・ナフタリン(共著者、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 blog を参照してください。
「クラスがこのクラスを拡張する場合、パラメーター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(){...}
}
Enum
ソースコードを見ると、次のようになっています。
_public abstract class Enum<E extends Enum<E>>
implements Comparable<E>, Serializable {
public final int compareTo(E o) {
Enum<?> other = (Enum<?>)o;
Enum<E> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
@SuppressWarnings("unchecked")
public final Class<E> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<E>)clazz : (Class<E>)zuper;
}
public static <T extends Enum<T>> T valueOf(Class<T> enumType,
String name) {
T result = enumType.enumConstantDirectory().get(name);
if (result != null)
return result;
if (name == null)
throw new NullPointerException("Name is null");
throw new IllegalArgumentException(
"No enum constant " + enumType.getCanonicalName() + "." + name);
}
}
_
まず最初に、_E extends Enum<E>
_はどういう意味ですか?これは、型パラメーターがEnumから拡張されたものであり、未加工の型ではパラメーター化されていないことを意味します(それ自体でパラメーター化されます)。
これは、列挙型がある場合に関連します
_public enum MyEnum {
THING1,
THING2;
}
_
私が正しく知っていれば、それはに翻訳されます
_public final class MyEnum extends Enum<MyEnum> {
public static final MyEnum THING1 = new MyEnum();
public static final MyEnum THING2 = new MyEnum();
}
_
したがって、これはMyEnumが次のメソッドを受け取ることを意味します。
_public final int compareTo(MyEnum o) {
Enum<?> other = (Enum<?>)o;
Enum<MyEnum> self = this;
if (self.getClass() != other.getClass() && // optimization
self.getDeclaringClass() != other.getDeclaringClass())
throw new ClassCastException();
return self.ordinal - other.ordinal;
}
_
さらに重要なことは、
_ @SuppressWarnings("unchecked")
public final Class<MyEnum> getDeclaringClass() {
Class<?> clazz = getClass();
Class<?> zuper = clazz.getSuperclass();
return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
}
_
これにより、getDeclaringClass()
が適切な_Class<T>
_オブジェクトにキャストされます。
より明確な例は、私が この質問 で答えたものです。ここでは、ジェネリックバウンドを指定したい場合、この構造を避けることはできません。