私はJavaのジェネリックをかなりよく理解していると思っていましたが、java.lang.Enumで次のことに遭遇しました。
class Enum<E extends Enum<E>>
誰かがこの型パラメーターを解釈する方法を説明できますか?同様の型パラメーターを使用できる場所の他の例を提供するためのボーナスポイント。
私はJavaのジェネリックをかなりよく理解していると思っていましたが、java.lang.Enumで次のことに遭遇しました。
class Enum<E extends Enum<E>>
誰かがこの型パラメーターを解釈する方法を説明できますか?同様の型パラメーターを使用できる場所の他の例を提供するためのボーナスポイント。
回答:
つまり、列挙型の型引数は、それ自体が同じ型引数を持つ列挙型から派生する必要があります。これはどうして起こりますか?型引数を新しい型自体にすることによって。したがって、StatusCodeと呼ばれる列挙型がある場合、それは次と同等になります。
public class StatusCode extends Enum<StatusCode>
制約を確認すると、次の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
それ自体は問題ありません。
Enum
型パラメーターの型を返すインスタンスメソッドはありません。
class Enum<E>
、すべてのケースでそれで十分だと主張しています。そして、ジェネリックスでは、型の安全性を確保するために実際に必要な場合にのみ、より制限的な境界を使用する必要があります。
Enum
サブクラスは常に自動生成されていなかった、あなたが必要となる唯一の理由class Enum<E extends Enum<?>>
オーバーがclass Enum<E>
アクセスする機能であるordinal
ためcompareTo()
。ただし、考えてみると、言語の観点からは、序数を介して2種類の列挙型を比較することはできません。したがって、Enum.compareTo()
その使用の実装は、自動生成されるサブクラスordinal
のコンテキストでのみ意味がありますEnum
。手動でサブクラス化できる場合はEnum
、compareTo
おそらくそうする必要がありますabstract
。
以下は、この本からの説明の修正版であるJavaのジェネリックとコレクション:私たちは持っている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)
Season
実装を主張したくないですComparable<Season>
か?
compareTo
ている必要があります。そうでない場合、コンパイラは序数がないと(正しく)言うでしょう。Enum
Enum
はclass OneEnum extends Enum<AnotherEnum>{}
、Enum
現在宣言されている方法でも、を持つことが可能です。それはとても、その後、別のと列挙の1種類を比較できるようにするにはあまり意味がありませんEnum
さんがcompareTo
とにかく宣言として意味がありません。境界線はこれを支援しません。
public class Enum<E extends Enum<?>>
十分です。
これは、簡単な例と、サブクラスのチェーンメソッド呼び出しを実装するために使用できる手法によって説明できます。以下の例で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!
}
}
return (CHILD) this;
:getThis()メソッド追加することを検討してくださいprotected CHILD getThis() { return this; }
:参照angelikalanger.com/GenericsFAQ/FAQSections/...
Node<T>
場合(そうでない場合)、時間を節約するために無視します。
return (SELF) this;
がにコンパイルされているreturn this;
ので、そのままにしておくことができます。
それが何を意味するのかと思っているのはあなただけではありません。Chaotic Javaブログを参照してください。
「クラスがこのクラスを拡張する場合、パラメーターEを渡す必要があります。パラメーターEの境界は、同じパラメーターEでこのクラスを拡張するクラス用です。」
この投稿では、「再帰的なジェネリック型」のこれらの問題が完全に明らかになりました。この特定の構造が必要なケースをもう1つ追加したかっただけです。
一般的なグラフに一般的なノードがあるとします。
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(){...}
}
class Foo extends Node<City>
、FooがCityと無関係の場所を作成することができます。
class Node<T>
ますか?
class Node<T>
あなたの例と完全に一致しています。
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>
オブジェクトにキャストされます。
より明確な例は、この質問で私が回答したものですが、一般的な境界を指定する場合は、この構成を回避できません。
compareTo
やバインドgetDeclaringClass
が必要なものはありませんextends Enum<E>
。
ウィキペディアによると、このパターンは「不思議な繰り返しテンプレートパターン」と呼ばれています。基本的に、CRTPパターンを使用することで、型キャストを行わなくてもサブクラス型を簡単に参照できます。つまり、パターンを使用することで、仮想関数を模倣できます。