Java-Enum-Definition

Lesezeit: 13 Minuten

Java Enum Definition
Donal

Ich dachte, ich hätte Java-Generika ziemlich gut verstanden, 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.

Java Enum Definition
Jon Skeet

Das bedeutet, dass das Typargument für Aufzählung von einer Aufzählung abgeleitet werden muss, die selbst das gleiche Typargument hat. Wie kann das passieren? Indem das Typargument zum neuen Typ selbst gemacht wird. Wenn ich also eine Aufzählung namens StatusCode habe, wäre dies äquivalent zu:

public class StatusCode extends Enum<StatusCode>

Wenn Sie jetzt die Einschränkungen überprüfen, haben wir Enum<StatusCode> – damit E=StatusCode. Lassen Sie uns überprüfen: tut E erweitern Enum<StatusCode>? Jawohl! Wir sind in Ordnung.

Sie fragen sich vielleicht, was das soll 🙂 Nun, es bedeutet, dass die API für Enum auf sich selbst verweisen kann – zum Beispiel in der Lage zu sein, das zu sagen Enum<E> implementiert Comparable<E>. Die Basisklasse kann die Vergleiche durchführen (im Fall von Aufzählungen), aber sie kann sicherstellen, dass sie nur die richtigen Arten von Aufzählungen miteinander vergleicht. (EDIT: Nun, fast – siehe die Bearbeitung unten.)

Ich habe etwas Ähnliches in meinem C#-Port von ProtocolBuffers verwendet. Es gibt „Messages“ (unveränderlich) und „Builder“ (veränderlich, um eine Nachricht zu erstellen) – und sie kommen als Typenpaare vor. 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>

Das bedeutet, dass Sie von einer Nachricht einen geeigneten Builder erhalten können (z. B. um eine Kopie einer Nachricht zu nehmen 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, dass Benutzer der API sich nicht wirklich darum kümmern müssen – es ist entsetzlich 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 gleichen Typ ist. Zweck ist die Gewährung von Vorteilen in der rechts Fall, anstatt Sie vor dem zu schützen falsch Fall.

Also wenn Enum wurden in Java sowieso nicht “speziell” behandelt, Sie könnten (wie in den Kommentaren erwähnt) die folgenden Typen erstellen:

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

Second umsetzen würde Comparable<First> eher, als Comparable<Second>… aber First selbst wäre in Ordnung.

  • @artsrc: Ich kann mich nicht auf Anhieb 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 es nicht gebraucht hätte 🙂

    – Jon Skeet

    22. März 2012 um 6:45 Uhr

  • @SayemAhmed: Ja, es verhindert nicht das Aspekt der Verwechslung der Typen. Ich werde eine Anmerkung dazu hinzufügen.

    – Jon Skeet

    18. Juli 2013 um 9:55 Uhr

  • “Ich habe etwas Ähnliches in meiner C#-Portierung von ProtocolBuffers verwendet.” Aber das ist anders, weil Builder über Instanzmethoden verfügen, die den Typparameter type zurückgeben. Enum hat keine Instanzmethoden, die den Typparameter type zurückgeben.

    – neues Konto

    25. Februar 2015 um 5:18 Uhr

  • @JonSkeet: Nun, 1) Enum kann nicht manuell unterklassiert werden, sodass bereits sichergestellt ist, dass der Parameter auch ohne die Grenze mit dem Typ identisch ist. 2) Wenn Enum manuell abgeleitet werden könnte, verhindert die Grenze nicht, dass der Typparameter ein anderer Aufzählungstyp als der Typ der Instanz ist. 3) Wenn du das sagst compareTo muss nur verwendet werden ordinal von irgendwelchen Enumdann eine Grenze von class Enum<E extends Enum<?>> würde reichen.

    – neues Konto

    25. Februar 2015 um 9:53 Uhr

  • @JonSkeet: Da Enum-Klassen immer automatisch generiert werden, behaupte ich das class Enum<E> ist in allen Fällen ausreichend. Und in Generics sollten Sie nur dann eine restriktivere Schranke verwenden, wenn dies zur Gewährleistung der Typsicherheit tatsächlich erforderlich ist.

    – neues Konto

    26. Februar 2015 um 0:01 Uhr

1645982834 64 Java Enum Definition
Moritz Naftalin

Das Folgende ist eine modifizierte Version der Erklärung aus dem Buch Java-Generika und -Sammlungen: Wir haben ein Enum erklärt

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 Voraussetzungen für Season ist, dass es implementieren sollte Comparable<Season>. Also werden wir brauchen

Season extends ... implements Comparable<Season>

Was könntest du gebrauchen ... damit würde das funktionieren? Da es sich um eine Parametrisierung von handeln muss Enumdie einzige Wahl ist Enum<Season>damit Sie Folgendes haben können:

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

Damit Enum ist auf Typen wie parametriert Season. Auszug aus Season und Sie erhalten, dass der Parameter von Enum ist jeder Typ, der erfüllt

 E extends Enum<E>

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

  • @newacct OK, ich verstehe jetzt: Sie möchten, dass alle Enums Instanzen von Enum sind, richtig? (Denn wenn es sich um Instanzen eines Enum-Untertyps handelt, gilt das obige Argument.) Aber dann haben Sie keine typsicheren Enums mehr, sodass Sie den Sinn verlieren, überhaupt Enums zu haben.

    – Maurice Naftalin

    15. Dezember 2013 um 0:17 Uhr

  • @newacct Sehen Sie sich die Definition von Enum an. Um eine Instanz mit einer anderen zu vergleichen, müssen ihre Ordnungszahlen verglichen werden. Also das Argument der compareTo Methode muss gewesen sein erklärt als ein Enum Untertyp, oder der Compiler wird (korrekterweise) sagen, dass es keine Ordnungszahl hat.

    – Maurice Naftalin

    19. Dezember 2013 um 12:35 Uhr

  • @MauriceNaftalin: Enum.compareTo() wird als Parameter vom Typ deklariert E. Dies ist eine Standardbibliotheksfunktion, und wie sie implementiert wird, ist nicht Sache des Benutzers. Die Java-Sprache verbietet Benutzern ausdrücklich das manuelle Erstellen von Unterklassen Enumund bei allen vom Compiler generierten Aufzählungen muss der Typparameter ohnehin mit dem Typ selbst identisch sein, sodass sich die Standardbibliotheksimplementierung darauf verlassen kann, um eine unsichere Umwandlung durchzuführen.

    – neues Konto

    25. Februar 2015 um 6:09 Uhr

  • @MauriceNaftalin: Wenn Java das manuelle Unterklassen nicht untersagt hat Enumdann wäre es möglich zu haben class OneEnum extends Enum<AnotherEnum>{}sogar mit wie Enum wird gerade erklärt. Es würde also nicht viel Sinn machen, eine Art von Aufzählung mit einer anderen vergleichen zu können Enum‘S compareTo würde wie deklariert sowieso keinen Sinn machen. Die Grenzen helfen dabei nicht weiter.

    – neues Konto

    25. Februar 2015 um 6:12 Uhr

  • @MauriceNaftalin: Wenn die Ordnungszahl der Grund war, dann public class Enum<E extends Enum<?>> würde auch reichen.

    – neues Konto

    25. Februar 2015 um 6:24 Uhr

1645982835 6 Java Enum Definition
Andrey Chaschev

Dies kann durch ein einfaches Beispiel und eine Technik veranschaulicht werden, die verwendet werden kann, um verkettete Methodenaufrufe für Unterklassen zu implementieren. In einem Beispiel unten setName gibt a zurück Node Verkettung funktioniert also nicht für die 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 City gibt jetzt den richtigen Typ zurück:

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 Umwandlung: return (CHILD) this; Erwägen Sie, eine getThis()-Methode hinzuzufügen: protected CHILD getThis() { return this; } Sehen: angelikalanger.com/GenericsFAQ/FAQSections/…

    – Roland

    28. November 2013 um 17:36 Uhr

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

    – Andrey Chaschev

    28. November 2013 um 22:46 Uhr

  • Ihre Bearbeitung unterscheidet sich nicht so sehr von der vorherigen, abgesehen davon, dass Sie etwas syntaktischen Zucker hinzufügen, bedenken Sie, dass der folgende Code tatsächlich kompiliert, aber einen Laufzeitfehler auslöst: ` Node node = new Node() .setName(“node”) . setSquare(1);` Wenn Sie sich den Java-Byte-Code ansehen, werden Sie feststellen, dass die Anweisung aufgrund der Typlöschung gelöscht wird return (SELF) this; einkompiliert wird return this;also könntest du es einfach weglassen.

    – Roland

    29. November 2013 um 14:39 Uhr


  • @Roland danke, das habe ich gebraucht – werde das Beispiel aktualisieren, wenn ich frei bin.

    – Andrey Chaschev

    29. November 2013 um 15:00 Uhr


  • Gut ist auch folgender Link: angelikalanger.com/GenericsFAQ/FAQSections/…

    – Roland

    29. November 2013 um 15:57 Uhr

Sie sind nicht der Einzige, der sich fragt, was das bedeutet; sehen 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“.

1645982836 460 Java Enum Definition
Nozebakel

Dieser Beitrag hat mir dieses Problem der “rekursiven generischen Typen” vollständig verdeutlicht. Ich wollte nur einen weiteren Fall hinzufügen, in dem diese besondere 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 spezialisierter 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> wobei Foo nichts mit City zu tun hat.

    – neues Konto

    23. November 2011 um 22:43 Uhr

  • Klar, und ist das falsch? Ich glaube nicht. Der von Node bereitgestellte Basisvertrag wird weiterhin eingehalten, nur Ihre Foo-Unterklasse ist weniger nützlich, da Sie anfangen, mit Foos zu arbeiten, aber Cities aus dem ADT herausholen. Es mag einen Anwendungsfall dafür geben, aber höchstwahrscheinlich ist es einfacher und nützlicher, den generischen Parameter einfach mit der Unterklasse identisch zu machen. Aber so oder so, der Designer hat diese Wahl.

    – mdma

    24. April 2012 um 11:30 Uhr

  • @mdma: Ich stimme zu. Was nützt dann die Grenze, über gerecht class Node<T>?

    – neues Konto

    30. November 2013 um 8:09 Uhr


  • @nozebacle: Ihr Beispiel zeigt nicht, dass “diese bestimmte Struktur notwendig ist”. class Node<T> stimmt voll und ganz mit deinem Beispiel überein.

    – neues Konto

    30. November 2013 um 8:10 Uhr

1645982836 359 Java Enum Definition
Gemeinschaft

Schaut man sich die an Enum Quellcode, es hat 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);
    } 
}

Das Wichtigste zuerst, was tut E extends Enum<E> bedeuten? Dies bedeutet, dass der Typparameter etwas ist, das sich von Enum aus erstreckt und nicht mit einem Rohtyp parametrisiert ist (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();
}

Das bedeutet also, 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;
    }

Das macht getDeclaringClass() zum richtigen werfen Class<T> Objekt.

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

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

    – neues Konto

    23. November 2011 um 22:43 Uhr

  • Klar, und ist das falsch? Ich glaube nicht. Der von Node bereitgestellte Basisvertrag wird weiterhin eingehalten, nur Ihre Foo-Unterklasse ist weniger nützlich, da Sie anfangen, mit Foos zu arbeiten, aber Cities aus dem ADT herausholen. Es mag einen Anwendungsfall dafür geben, aber höchstwahrscheinlich ist es einfacher und nützlicher, den generischen Parameter einfach mit der Unterklasse identisch zu machen. Aber so oder so, der Designer hat diese Wahl.

    – mdma

    24. April 2012 um 11:30 Uhr

  • @mdma: Ich stimme zu. Was nützt dann die Grenze, über gerecht class Node<T>?

    – neues Konto

    30. November 2013 um 8:09 Uhr


  • @nozebacle: Ihr Beispiel zeigt nicht, dass “diese bestimmte Struktur notwendig ist”. class Node<T> stimmt voll und ganz mit deinem Beispiel überein.

    – neues Konto

    30. November 2013 um 8:10 Uhr

1645982837 628 Java Enum Definition
Hanzhou Tang

Laut Wikipedia heißt dieses Muster Seltsam wiederkehrendes Schablonenmuster. Grundsätzlich können wir durch die Verwendung des CRTP-Musters leicht auf den Unterklassentyp ohne Typumwandlung verweisen, was bedeutet, dass wir durch die Verwendung des Musters eine virtuelle Funktion imitieren können.

878530cookie-checkJava-Enum-Definition

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy