Java Enumの定義


151

私はJavaのジェネリックをかなりよく理解していると思っていましたが、java.lang.Enumで次のことに遭遇しました。

class Enum<E extends Enum<E>>

誰かがこの型パラメーターを解釈する方法を説明できますか?同様の型パラメーターを使用できる場所の他の例を提供するためのボーナスポイント。


9
これが私が一番好きな説明です:Groking Enum(別名Enum&lt; E extends Enum&lt; E >>)
Alan Moore

この質問にはより良い回答があります:stackoverflow.com/a/3068001/2413303
EpicPandaForce

回答:


105

つまり、列挙型の型引数は、それ自体が同じ型引数を持つ列挙型から派生する必要があります。これはどうして起こりますか?型引数を新しい型自体にすることによって。したがって、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それ自体は問題ありません。


1
@artsrc:ビルダーとメッセージの両方で汎用的である必要がある理由をすぐには思い出せません。私がそれを必要としなかったなら、私はそのルートを下っていなかっただろうと確信しています:)
Jon Skeet

1
@SayemAhmed:はい、それはタイプを混同するその側面を妨げません。これについてのメモを追加します。
Jon Skeet 2013

1
「ProtocolBuffersのC#ポートで同様のものを使用しました。」ただし、ビルダーには型パラメーターの型を返すインスタンスメソッドがあるため、これは異なります。Enum型パラメーターの型を返すインスタンスメソッドはありません。
newacct 2015

1
@JonSkeet:enumクラスは常に自動生成されるのでclass Enum<E>、すべてのケースでそれで十分だと主張しています。そして、ジェネリックスでは、型の安全性を確保するために実際に必要な場合にのみ、より制限的な境界を使用する必要があります。
newacct 2015

1
@JonSkeetは、次の場合も、Enumサブクラスは常に自動生成されていなかった、あなたが必要となる唯一の理由class Enum<E extends Enum<?>>オーバーがclass Enum<E>アクセスする機能であるordinalためcompareTo()。ただし、考えてみると、言語の観点からは、序数を介して2種類の列挙型を比較す​​ることはできません。したがって、Enum.compareTo()その使用の実装は、自動生成されるサブクラスordinalのコンテキストでのみ意味がありますEnum。手動でサブクラス化できる場合はEnumcompareToおそらくそうする必要がありますabstract
newacct 2015

27

以下は、この本からの説明の修正版である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)


1
@newacct OK、わかりました。すべての列挙型をEnum <E>のインスタンスにしたいですね。(それらがEnumサブタイプのインスタンスである場合、上記の引数が適用されます。)しかし、その場合は、タイプセーフな列挙型がなくなるため、列挙型を持つポイントが失われます。
モーリスナフタリン2013

1
@newacct Season実装を主張したくないですComparable<Season>か?
モーリスナフタリン2013

2
@newacct Enumの定義を見てください。あるインスタンスを別のインスタンスと比較するには、それらの序数を比較する必要があります。したがって、メソッドの引数はサブタイプとして宣言さcompareToている必要があります。そうでない場合、コンパイラは序数がないと(正しく)言うでしょう。Enum
モーリスナフタリン2013

2
@MauriceNaftalin:Javaが手動でのサブクラス化を禁止していなかった場合Enumclass OneEnum extends Enum<AnotherEnum>{}Enum現在宣言されている方法でも、を持つことが可能です。それはとても、その後、別のと列挙の1種類を比較できるようにするにはあまり意味がありませんEnumさんがcompareToとにかく宣言として意味がありません。境界線はこれを支援しません。
newacct 2015

2
@MauriceNaftalin:序数が理由である場合は、それでもpublic class Enum<E extends Enum<?>>十分です。
newacct 2015

6

これは、簡単な例と、サブクラスのチェーンメソッド呼び出しを実装するために使用できる手法によって説明できます。以下の例で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/...
ローランド

@Rolandリンクをありがとう、私はそこからアイデアを借りました。この特定のケースでこれが悪い習慣である理由を説明している記事に直接説明してもらえますか?リンク内のメソッドにはより多くのタイピングが必要であり、これが私がこれを避けている主な議論です。この場合、キャストエラーを見たことがありません+不可避のキャストエラーがあることを知っています。つまり、複数のタイプのオブジェクトを同じコレクションに格納する場合です。したがって、チェックされていないキャストが重要でなく、デザインが多少複雑なNode<T>場合(そうでない場合)、時間を節約するために無視します。
Andrey Chaschev 2013年

あなたの編集は、いくつかの構文糖を追加すること以外は以前とは異なり、次のコードは実際にはコンパイルされますがランタイムエラーがスローされることを考慮してください: `Node <City> node = new Node <City>().setName(" node ")。 setSquare(1); `Javaのバイトコードを見ると、型の消去により、ステートメントreturn (SELF) this;がにコンパイルされているreturn this;ので、そのままにしておくことができます。
Roland

@Rolandありがとう、これは私が必要としたものです-私が空いたときに例を更新します。
Andrey Chaschev 2013年


3

それが何を意味するのかと思っているのはあなただけではありません。Chaotic Javaブログを参照してください。

「クラスがこのクラスを拡張する場合、パラメーターEを渡す必要があります。パラメーターEの境界は、同じパラメーターEでこのクラスを拡張するクラス用です。」


1

この投稿では、「再帰的なジェネリック型」のこれらの問題が完全に明らかになりました。この特定の構造が必要なケースをもう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と無関係の場所を作成することができます。
newacct

1
確かに、それは間違っていますか?そうは思いません。Node <City>によって提供される基本コントラクトは依然として名誉です。Fooでの作業を開始し、ADTからCitiesを除外するため、Fooサブクラスの有用性は低くなります。これにはユースケースがあるかもしれませんが、ジェネリックパラメーターをサブクラスと同じにするだけのほうが、おそらくより単純でより便利です。しかしどちらにしても、デザイナーにはその選択があります。
mdma

@mdma:同意します。それでは、バウンドはどのように使用できclass Node<T>ますか?
newacct 2013年

1
@nozebacle:この例では、「この特定の構造が必要である」ことを示していません。class Node<T>あなたの例と完全に一致しています。
newacct 2013年

1

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>
newacct 2016年

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.