Frage

Ich dachte, ich Java Generics verstanden recht gut, aber dann kam ich auf die folgenden in java.lang.Enum:

class Enum<E extends Enum<E>>

Könnte jemand erklären, wie diese Art Parameter zu interpretieren? Bonuspunkte für die Bereitstellung andere Beispiele, wo ein ähnlicher Typ Parameter verwendet werden könnten.

War es hilfreich?

Lösung

Es bedeutet, dass die Art Argument für Enum aus einer Enum abzuleiten hat, die sich die gleiche Art Argument hat. Wie kann das geschehen? Indem der Typ Argument den neuen Typ selbst. Also, wenn ich eine ENUM habe Statuscode genannt, wäre es äquivalent zu:

public class StatusCode extends Enum<StatusCode>

Nun, wenn Sie die Einschränkungen überprüfen, haben wir Enum<StatusCode> bekommen - so E=StatusCode. Lassen Sie uns: Hat E Enum<StatusCode> verlängern? Ja! Wir sind in Ordnung.

Sie kann gut sein, sich zu fragen, was der Sinn dieser ist :) Nun, es bedeutet, dass die API für Enum auf sich selbst beziehen kann - zum Beispiel, dass Enum<E> implementiert Comparable<E> sagen zu können. Die Basisklasse in der Lage, die Vergleiche zu tun (im Fall von Aufzählungen), aber es kann sicherstellen, dass es nur die richtige Art von Aufzählungen miteinander vergleicht. (EDIT:. Nun ja, fast - siehe Bearbeiten an der Unterseite)

Ich habe etwas ähnliches in meinem C # Hafen von Protocol Buffers verwendet. Es gibt „Botschaften“ (unveränderlich) und „Builder“ (wandelbar, verwendet, um eine Nachricht zu bauen) - und sie kommen als Paare von Typen. Die Schnittstellen beteiligt 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, aus einer Nachricht, dass Sie einen geeigneten Builder bekommen können (zum Beispiel eine Kopie einer Nachricht zu nehmen und einige Bits aus) und von einem Builder können Sie eine entsprechende Meldung erhalten, wenn Sie fertig sind Gebäude es. Es ist eine gute Arbeit Benutzer der API brauchen nicht wirklich darüber obwohl kümmern -. Es ist horrend kompliziert und dauerte mehrere Iterationen zu bekommen, wo es ist

EDIT: Beachten Sie, dass dies nicht Sie stoppt aus ungeraden Typen zu schaffen, die eine Art Argument zu benutzen, die selbst in Ordnung ist, aber das ist nicht die gleiche Art. Der Zweck ist, Vorteile in der rechts Fall eher zu geben, als Sie schützen die falsch Fall.

Also, wenn Enum nicht behandelt wurde „besonders“ in Java wie auch immer, man kann (wie in den Kommentaren angegeben) erstellen die folgenden Typen:

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

Second würde implementieren Comparable<First> statt Comparable<Second> ... aber First selbst wäre in Ordnung.

Andere Tipps

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

enum Season { WINTER, SPRING, SUMMER, FALL }

, die zu einer Klasse erweitert werden

final class Season extends ...

wo ... ist die irgendwie parametrierte Basisklasse für Aufzählungen sein. Lass uns arbeiten Sie heraus, was das muss sein. Nun, eine der Voraussetzungen für Season ist, dass es Comparable<Season> implementieren sollte. Also wir gehen müssen

Season extends ... implements Comparable<Season>

Was könnten Sie für ... verwenden, die dies erlauben würde, zu arbeiten? Da es sich um eine Parametrisierung von Enum sein muss, ist die einzige Wahl Enum<Season>, so dass Sie haben können:

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

So Enum auf Typen wie Season parametriert. Auszug aus Season und Sie erhalten, dass der Parameter von Enum ist jede Art, die erfüllt

 E extends Enum<E>

Maurice Naftalin (Co-Autor, Java Generics und Sammlungen)

Dies kann durch ein einfaches Beispiel veranschaulicht werden und eine Technik, die verwendet werden kann, verkettete Verfahren zu implementieren fordert Unterklassen. In einem Beispiel unter setName eine Node liefert so Chaining für die City wird nicht funktionieren:

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
}

So wir eine Unterklasse in einer allgemeinen Erklärung verweisen könnten, so dass der City jetzt gibt den richtigen Typen:

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

Sie sind nicht der einzige fragen, was das bedeutet; finden Sie unter Chaotic Java Blog .

„Wenn eine Klasse diese Klasse erweitert, es sollte einen Parameter E. Den Parameter E der Grenzen sind für eine Klasse übergeben, die diese Klasse mit dem gleichen Parameter erweitert E“.

Dieser Beitrag wird völlig mir dieses Problem der ‚rekursiven generischer Typen‘ geklärt. Ich wollte nur einen anderen Fall hinzufügen, wo diese besondere Struktur erforderlich 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 Spezialtypen haben:

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

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

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

Wenn Sie an der Enum Quellcode schauen, hat es die folgenden:

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 erste, was zuerst, was bedeutet E extends Enum<E>? Es bedeutet, dass der Typ-Parameter ist etwas, das von Enum erstreckt, und ist nicht mit einer rohen Art (es ist parametrisiert von selbst) parametrisiert.

Dies ist relevant, wenn Sie eine ENUM haben

public enum MyEnum {
    THING1,
    THING2;
}

, die, wenn ich mich richtig kennen, wird übersetzt

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

Dies 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 was noch wichtiger ist,

    @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() Guss auf das richtige Class<T> Objekt.

Ein Weg deutlicheres Beispiel ist die, die ich auf beantwortet diese Frage , wo man dies nicht vermeiden können, konstruieren, wenn Sie eine allgemeine Grenze angeben möchten.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top