Java Enum Definition


151

Ich dachte, ich verstehe Java-Generika ziemlich gut, aber dann bin ich in java.lang.Enum auf Folgendes gestoßen:

class Enum<E extends Enum<E>>

Könnte jemand erklären, wie dieser Typparameter zu interpretieren ist? Bonuspunkte für die Bereitstellung anderer Beispiele dafür, wo ein ähnlicher Typparameter verwendet werden könnte.


9
Hier ist die Erklärung, die mir am besten gefällt: Groking Enum (auch bekannt als Enum & lt; E erweitert Enum & lt; E >>)
Alan Moore

Diese Frage hat bessere Antworten: stackoverflow.com/a/3068001/2413303
EpicPandaForce

Antworten:


105

Dies bedeutet, dass das Typargument für enum von einer Aufzählung abgeleitet werden muss, die selbst dasselbe Typargument hat. Wie kann das passieren? Indem Sie das Typargument zum neuen Typ selbst machen. Wenn ich also eine Aufzählung mit dem Namen StatusCode habe, entspricht dies:

public class StatusCode extends Enum<StatusCode>

Wenn Sie nun die Einschränkungen überprüfen, haben wir Enum<StatusCode>- also E=StatusCode. Lassen Sie uns überprüfen: Everlängert Enum<StatusCode>? Ja! Wir sind okay.

Sie fragen sich vielleicht, worum es geht :) Nun, das bedeutet, dass die API für Enum auf sich selbst verweisen kann - zum Beispiel, wenn Sie sagen können, dass dies Enum<E>implementiert ist Comparable<E>. Die Basisklasse kann die Vergleiche durchführen (im Fall von Aufzählungen), aber sie kann sicherstellen, dass nur die richtige Art von Aufzählungen miteinander verglichen wird. (EDIT: Nun, fast - siehe die Bearbeitung unten.)

Ich habe etwas Ähnliches in meinem C # -Port von ProtocolBuffers verwendet. Es gibt "Nachrichten" (unveränderlich) und "Builder" (veränderlich, zum Erstellen einer Nachricht verwendet) - und sie kommen als Typenpaare. Die beteiligten Schnittstellen sind:

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>

Dies bedeutet, dass Sie aus einer Nachricht einen geeigneten Builder erhalten können (z. B. um eine Kopie einer Nachricht zu erstellen und einige Bits zu ändern), und von einem Builder können Sie eine entsprechende Nachricht erhalten, wenn Sie mit dem Erstellen fertig sind. Es ist ein guter Job, den Benutzer der API nicht wirklich interessieren müssen - es ist schrecklich kompliziert und es dauerte mehrere Iterationen, um dorthin zu gelangen, wo es ist.

BEARBEITEN: Beachten Sie, dass dies Sie nicht davon abhält, ungerade Typen zu erstellen, die ein Typargument verwenden, das selbst in Ordnung ist, aber nicht vom selben Typ ist. Der Zweck besteht darin, Vorteile im richtigen Fall zu bieten, anstatt Sie vor dem falschen Fall zu schützen .

Wenn EnumSie also in Java sowieso nicht "speziell" behandelt würden, könnten Sie (wie in den Kommentaren angegeben) die folgenden Typen erstellen:

public class First extends Enum<First> {}
public class Second extends Enum<First> {}

Secondwürde Comparable<First>eher implementieren als Comparable<Second>... aber Firstselbst wäre in Ordnung.


1
@artsrc: Ich kann mich nicht ohne weiteres erinnern, warum es sowohl im Builder als auch in der Nachricht generisch sein muss. Ich bin mir ziemlich sicher, dass ich diesen Weg nicht gegangen wäre, wenn ich ihn nicht gebraucht hätte :)
Jon Skeet

1
@SayemAhmed: Ja, es verhindert nicht diesen Aspekt des Verwechselns der Typen. Ich werde eine Notiz dazu hinzufügen.
Jon Skeet

1
"Ich habe etwas Ähnliches in meinem C # -Port von ProtocolBuffers verwendet." Dies ist jedoch anders, da Builder Instanzmethoden haben, die den Typparametertyp zurückgeben. EnumEs gibt keine Instanzmethoden, die den Typparametertyp zurückgeben.
Newacct

1
@ JonSkeet: Da Aufzählungsklassen immer automatisch generiert werden, behaupte ich, dass dies class Enum<E>in allen Fällen ausreichend ist. Und in Generika sollten Sie eine restriktivere Bindung nur verwenden, wenn dies tatsächlich erforderlich ist, um die Typensicherheit zu gewährleisten.
Newacct

1
@JonSkeet: Auch wenn EnumSubklassen der einzige Grund , nicht immer automatisch generiert wurden, müssen Sie class Enum<E extends Enum<?>>über class Enum<E>die Fähigkeit, Zugang ordinalzu compareTo(). Wenn Sie jedoch darüber nachdenken, ist es aus sprachlicher Sicht nicht sinnvoll, zwei verschiedene Arten von Aufzählungen über ihre Ordnungszahlen zu vergleichen. Daher ist die Implementierung Enum.compareTo()dieser Verwendungen ordinalnur im Zusammenhang mit der Enumautomatischen Generierung von Unterklassen sinnvoll . Wenn Sie manuell Unterklasse könnten Enum, compareTomüsste wahrscheinlich sein abstract.
Newacct

27

Das Folgende ist eine modifizierte Version der Erklärung aus dem Buch Java Generics and Collections : Wir haben eine Enumdeklarierte

enum Season { WINTER, SPRING, SUMMER, FALL }

die zu einer Klasse erweitert wird

final class Season extends ...

wo ...soll die irgendwie parametrisierte Basisklasse für Enums sein. Lassen Sie uns herausfinden, was das sein muss. Nun, eine der Anforderungen dafür Seasonist, dass es implementiert werden sollte Comparable<Season>. Also werden wir brauchen

Season extends ... implements Comparable<Season>

Was könnten Sie dafür verwenden ..., damit dies funktioniert? Da es sich um eine Parametrisierung von handeln muss Enum, besteht die einzige Wahl darin Enum<Season>, dass Sie Folgendes haben können:

Season extends Enum<Season>
Enum<Season> implements Comparable<Season>

So Enumwird auf Typen wie parametriert Season. Wenn Sie von abstrahieren, erhalten SeasonSie, dass der Parameter von Enumein beliebiger Typ ist, der erfüllt

 E extends Enum<E>

Maurice Naftalin (Co-Autor, Java Generics and Collections)


1
@newacct OK, ich verstehe es jetzt: Sie möchten, dass alle Aufzählungen Instanzen von Aufzählung <E> sind, oder? (Wenn es sich um Instanzen eines Enum-Subtyps handelt, gilt das obige Argument.) Dann haben Sie jedoch keine typsicheren Enums mehr, sodass Sie den Punkt verlieren, überhaupt Enums zu haben.
Maurice Naftalin

1
@newacct Willst du nicht darauf bestehen, dass Seasonimplementiert Comparable<Season>?
Maurice Naftalin

2
@newacct Schauen Sie sich die Definition von Enum an. Um eine Instanz mit einer anderen zu vergleichen, müssen ihre Ordnungszahlen verglichen werden. Das Argument der compareToMethode muss also als Subtyp deklariert worden sein, sonst Enumsagt der Compiler (korrekt), dass es keine Ordnungszahl gibt.
Maurice Naftalin

2
@MauriceNaftalin: Wenn Java die manuelle Unterklassifizierung nicht verboten Enumhätte, wäre dies möglich class OneEnum extends Enum<AnotherEnum>{}, selbst wenn die derzeitige EnumDeklaration erfolgt. Es würde nicht viel Sinn in der Lage machen mit einer anderen Art von ENUM zu vergleichen, so ist, dann Enumist compareTonicht sinnvoll wie eh erklärt. Die Grenzen helfen dabei nicht weiter.
Newacct

2
@ MauriceNaftalin: Wenn die Ordnungszahl der Grund wäre, public class Enum<E extends Enum<?>>würde das auch ausreichen.
Newacct

6

Dies kann durch ein einfaches Beispiel und eine Technik veranschaulicht werden, mit der verkettete Methodenaufrufe für Unterklassen implementiert werden können. In einem Beispiel unten wird setNameeine zurückgegeben, Nodesodass die Verkettung für Folgendes nicht funktioniert 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
}

Wir könnten also in einer generischen Deklaration auf eine Unterklasse verweisen, sodass die Cityjetzt den richtigen Typ zurückgibt:

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!
    }
}

Ihre Lösung hat eine ungeprüfte Besetzung: return (CHILD) this;Erwägen Sie das Hinzufügen einer getThis () -Methode: protected CHILD getThis() { return this; } Siehe: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

@ Roland danke für einen Link, ich habe eine Idee davon ausgeliehen. Könnten Sie mich erklären oder direkt auf einen Artikel verweisen, in dem erklärt wird, warum dies in diesem speziellen Fall eine schlechte Praxis ist? Die Methode im Link erfordert mehr Eingabe und dies ist das Hauptargument, warum ich dies vermeide. Ich habe in diesem Fall noch nie Besetzungsfehler gesehen + Ich weiß, dass es einige unvermeidliche Besetzungsfehler gibt - dh wenn man Objekte mehrerer Typen in derselben Sammlung speichert. Wenn also ungeprüfte Besetzungen nicht kritisch sind und das Design etwas komplex ist ( Node<T>was nicht der Fall ist), ignoriere ich sie, um Zeit zu sparen.
Andrey Chaschev

Ihre Bearbeitung unterscheidet sich nicht wesentlich von der vorherigen, abgesehen vom Hinzufügen von syntaktischem Zucker. Beachten Sie jedoch, dass der folgende Code tatsächlich kompiliert wird, aber einen Laufzeitfehler auslöst: `Node <City> node = new Node <City> () .setName (" node "). setSquare (1); `Wenn Sie sich den Java-Byte-Code ansehen, werden Sie feststellen , dass die Anweisung aufgrund des Löschens des Typs return (SELF) this;kompiliert wird return this;, sodass Sie sie einfach weglassen können.
Roland

@ Roland danke, das ist was ich brauchte - wird das Beispiel aktualisieren, wenn ich frei bin.
Andrey Chaschev

Der folgende Link ist auch gut: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

3

Sie sind nicht der einzige, der sich fragt, was das bedeutet. Siehe Chaotischer Java-Blog .

"Wenn eine Klasse diese Klasse erweitert, sollte sie einen Parameter E übergeben. Die Grenzen des Parameters E gelten für eine Klasse, die diese Klasse mit demselben Parameter E erweitert."


1

Dieser Beitrag hat mir dieses Problem der 'rekursiven generischen Typen' vollständig geklärt. Ich wollte nur einen weiteren Fall hinzufügen, in dem diese spezielle Struktur notwendig ist.

Angenommen, Sie haben generische Knoten in einem generischen Diagramm:

public abstract class Node<T extends Node<T>>
{
    public void addNeighbor(T);

    public void addNeighbors(Collection<? extends T> nodes);

    public Collection<T> getNeighbor();
}

Dann können Sie Diagramme von speziellen Typen haben:

public class City extends Node<City>
{
    public void addNeighbor(City){...}

    public void addNeighbors(Collection<? extends City> nodes){...}

    public Collection<City> getNeighbor(){...}
}

Es erlaubt mir immer noch, eine zu erstellen, class Foo extends Node<City>in der Foo nichts mit City zu tun hat.
Newacct

1
Sicher, und ist das falsch? Das glaube ich nicht. Der von Node <City> bereitgestellte Basisvertrag wird weiterhin eingehalten. Nur Ihre Foo-Unterklasse ist weniger nützlich, da Sie mit Foos arbeiten, Städte jedoch aus dem ADT entfernen. Möglicherweise gibt es hierfür einen Anwendungsfall, der jedoch höchstwahrscheinlich einfacher und nützlicher ist, wenn der generische Parameter nur mit der Unterklasse identisch ist. Aber so oder so hat der Designer diese Wahl.
Mdma

@ MDMA: Ich stimme zu. Welchen Nutzen bietet die Bindung dann über gerecht class Node<T>?
Newacct

1
@nozebacle: Ihr Beispiel zeigt nicht, dass "diese bestimmte Struktur notwendig ist". class Node<T>stimmt voll und ganz mit Ihrem Beispiel überein.
Newacct

1

Wenn Sie sich den EnumQuellcode ansehen , hat er Folgendes:

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);
    } 
}

Was bedeutet als erstes E extends Enum<E>? Dies bedeutet, dass der Typparameter von Enum ausgeht und nicht mit einem Rohtyp parametrisiert wird (er wird von selbst parametrisiert).

Dies ist relevant, wenn Sie eine Aufzählung haben

public enum MyEnum {
    THING1,
    THING2;
}

was, wenn ich es richtig weiß, übersetzt wird

public final class MyEnum extends Enum<MyEnum> {
    public static final MyEnum THING1 = new MyEnum();
    public static final MyEnum THING2 = new MyEnum();
}

Dies bedeutet, dass MyEnum die folgenden Methoden erhält:

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;
}

Und noch wichtiger:

    @SuppressWarnings("unchecked")
    public final Class<MyEnum> getDeclaringClass() {
        Class<?> clazz = getClass();
        Class<?> zuper = clazz.getSuperclass();
        return (zuper == Enum.class) ? (Class<MyEnum>)clazz : (Class<MyEnum>)zuper;
    }

Dadurch wird getDeclaringClass()auf das richtige Class<T>Objekt gegossen .

Ein klareres Beispiel ist das, das ich auf diese Frage beantwortet habe, bei der Sie dieses Konstrukt nicht vermeiden können, wenn Sie eine generische Grenze angeben möchten.


Nichts, was Sie gezeigt haben compareTooder was getDeclaringClassdie extends Enum<E>Bindung erfordert .
Newacct

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.