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.
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
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
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.
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“.
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
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;
}
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
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.
Hier ist die Erklärung, die mir am besten gefällt: Groking Enum (auch bekannt als Enum<E erweitert Enum<E>>)
– Alan Moore
1. Januar 2014 um 3:26 Uhr
Diese Frage hat bessere Antworten: stackoverflow.com/a/3068001/2413303
– EpicPandaForce
5. Juni 2015 um 13:22 Uhr
schau mal sitepoint.com/self-types-with-javas-generics
– Arne Burmeister
18. Februar 2020 um 15:09 Uhr