Domanda

Pensavo di aver capito abbastanza bene i generici Java, ma poi ho trovato quanto segue in java.lang.Enum:

class Enum<E extends Enum<E>>

Qualcuno potrebbe spiegare come interpretare questo parametro di tipo? Punti bonus per la fornitura di altri esempi di utilizzo di un parametro di tipo simile.

È stato utile?

Soluzione

Significa che l'argomento tipo per enum deve derivare da un enum che a sua volta ha lo stesso argomento tipo. Come può succedere? Rendendo l'argomento type il nuovo tipo stesso. Quindi, se ho un enum chiamato StatusCode, sarebbe equivalente a:

public class StatusCode extends Enum<StatusCode>

Ora se controlli i vincoli, abbiamo Enum < StatusCode > - quindi E = StatusCode . Controlliamo: E estende Enum < StatusCode > ? Sì! Siamo a posto.

Potresti chiederti qual è il punto di questo :) Bene, significa che l'API per Enum può riferirsi a se stessa - ad esempio, essere in grado di dire che Enum < E > implementa Comparable < E > . La classe base è in grado di fare i confronti (nel caso di enumerazioni) ma può assicurarsi che confronta solo il giusto tipo di enumerazioni tra loro. (EDIT: Beh, quasi - vedi la modifica in fondo.)

Ho usato qualcosa di simile nella mia porta C # di ProtocolBuffers. Ci sono " messaggi " (immutabile) e "costruttori" (modificabile, usato per creare un messaggio) - e vengono come coppie di tipi. Le interfacce coinvolte sono:

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>

Ciò significa che da un messaggio puoi ottenere un builder appropriato (ad es. per prendere una copia di un messaggio e cambiare alcuni bit) e da un builder puoi ottenere un messaggio appropriato quando hai finito di costruirlo. È comunque un buon lavoro, gli utenti dell'API non devono preoccuparsene davvero - è terribilmente complicato e ha impiegato diverse iterazioni per arrivare dove si trova.

EDIT: tieni presente che ciò non ti impedisce di creare tipi dispari che utilizzano un argomento di tipo che va bene, ma che non è lo stesso tipo. Lo scopo è quello di offrire vantaggi nel caso giusto piuttosto che proteggerti dal caso sbagliato .

Quindi se Enum non sono stati gestiti " specialmente " comunque in Java, è possibile (come indicato nei commenti) creare i seguenti tipi:

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

Second implementerebbe Comparable < First > anziché Comparable < Second > ... ma First stesso andrebbe bene.

Altri suggerimenti

La seguente è una versione modificata della spiegazione del libro Java Generics and Collections : Abbiamo un Enum dichiarato

enum Season { WINTER, SPRING, SUMMER, FALL }

che verrà espanso in una classe

final class Season extends ...

dove ... deve essere la classe base in qualche modo parametrizzata per Enums. Lavoriamo quello che deve essere. Bene, uno dei requisiti per Season è che dovrebbe implementare Comparable < Season > . Quindi avremo bisogno

Season extends ... implements Comparable<Season>

Cosa potresti usare per ... che permetterebbe a questo di funzionare? Dato che deve essere una parametrizzazione di Enum , l'unica scelta è Enum < Season > , in modo da poter avere:

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

Quindi Enum è parametrizzato su tipi come Season . Estratto da Season e si ottiene che il parametro di Enum è qualsiasi tipo che soddisfa

 E extends Enum<E>

Maurice Naftalin (coautore, Java Generics and Collections)

Questo può essere illustrato da un semplice esempio e da una tecnica che può essere utilizzata per implementare chiamate di metodi concatenate per le sottoclassi. In un esempio di seguito setName restituisce un Node in modo che il concatenamento non funzioni per 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
}

Quindi potremmo fare riferimento a una sottoclasse in una dichiarazione generica, in modo che City ora restituisca il tipo corretto:

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

Non sei l'unico a chiederti cosa significhi; vedi Blog Java caotico .

& # 8220; Se una classe estende questa classe, dovrebbe passare un parametro E. I limiti del parametro E sono per una classe che estende questa classe con lo stesso parametro E & # 8221 ;.

Questo post mi ha chiarito totalmente questo problema di "tipi generici ricorsivi". Volevo solo aggiungere un altro caso in cui questa particolare struttura fosse necessaria.

Supponi di avere nodi generici in un grafico generico:

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

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

    public Collection<T> getNeighbor();
}

Quindi puoi avere grafici di tipi specializzati:

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

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

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

Se guardi il codice sorgente Enum , ha il seguente:

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

Per prima cosa, cosa significa E estende Enum < E > ? Significa che il parametro type è qualcosa che si estende da Enum e non è parametrizzato con un tipo raw (è parametrizzato da solo).

Questo è rilevante se hai un enum

public enum MyEnum {
    THING1,
    THING2;
}

che, se lo so correttamente, viene tradotto in

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

Quindi questo significa che MyEnum riceve i seguenti metodi:

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

E ancora più importante,

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

Questo rende getDeclaringClass () il corretto oggetto Class < T > .

Un esempio molto più chiaro è quello a cui ho risposto questa domanda in cui non è possibile evitare questo costrutto se si desidera specificare un limite generico.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top