Definizione di Java Enum


151

Pensavo di aver capito abbastanza bene i generici Java, ma poi mi sono imbattuto in java.lang.Enum:

class Enum<E extends Enum<E>>

Qualcuno potrebbe spiegare come interpretare questo parametro di tipo? Punti bonus per fornire altri esempi di dove potrebbe essere utilizzato un parametro di tipo simile.


9
Ecco la spiegazione che mi piace di più: Groking Enum (aka Enum & lt; E estende Enum & lt; E >>)
Alan Moore

Questa domanda ha risposte migliori: stackoverflow.com/a/3068001/2413303
EpicPandaForce

Risposte:


105

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>- così E=StatusCode. Controlliamo: si Eestende Enum<StatusCode>? Sì! Stiamo bene

Potresti chiederti qual è il punto di questo :) Bene, significa che l'API per Enum può fare riferimento 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" (immutabili) e "costruttori" (mutabili, usati per costruire 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 è possibile ottenere un builder appropriato (ad es. Per prendere una copia di un messaggio e modificare alcuni bit) e da un builder è possibile ottenere un messaggio appropriato al termine della costruzione. È 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: Nota che questo non ti impedisce di creare tipi dispari che usano un argomento di tipo che va bene, ma che non è lo stesso tipo. Lo scopo è quello di dare benefici nel caso giusto piuttosto che proteggerti dal caso sbagliato .

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

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

Secondimplementerebbe Comparable<First>piuttosto che Comparable<Second>... ma Firstandrebbe bene.


1
@artsrc: non ricordo con la mano perché debba essere generico sia nel builder che nel messaggio. Sono abbastanza sicuro che non avrei percorso quella strada se non ne avessi avuto bisogno :)
Jon Skeet

1
@SayemAhmed: Sì, non impedisce che aspetto di mescolare i tipi. Aggiungerò una nota a riguardo.
Jon Skeet,

1
"Ho usato qualcosa di simile nella mia porta C # di ProtocolBuffers." Ma questo è diverso perché i costruttori hanno metodi di istanza che restituiscono il tipo di parametro type. Enumnon ha metodi di istanza che restituiscono il tipo di parametro type.
nuovo

1
@JonSkeet: dato che le classi enum sono sempre autogenerate, sto affermando che class Enum<E>è sufficiente in tutti i casi. E in Generics dovresti usare un limite più restrittivo solo se è effettivamente necessario per garantire la sicurezza del tipo.
newacct,

1
@JonSkeet: Inoltre, se Enumsottoclassi non sono stati sempre generati automaticamente, l'unica ragione per cui si avrebbe bisogno class Enum<E extends Enum<?>>sopra class Enum<E>è la possibilità di accesso ordinalper compareTo(). Tuttavia, se ci pensate, non ha senso dal punto di vista linguistico permettervi di confrontare due diversi tipi di enumerazioni tramite i loro ordinali. Pertanto, l'implementazione di Enum.compareTo()tali usi ordinalha senso solo nel contesto della Enumgenerazione automatica delle sottoclassi. Se si potesse sottoclasse manualmente Enum, compareToprobabilmente dovrebbe esserlo abstract.
newacct,

27

La seguente è una versione modificata della spiegazione del libro Java Generics and Collections : ne abbiamo Enumdichiarato uno

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. Scopriamo cosa deve essere. Bene, uno dei requisiti Seasonè che dovrebbe implementare Comparable<Season>. Quindi avremo bisogno

Season extends ... implements Comparable<Season>

Cosa potresti usare per ...permettere 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 Seasone ottieni che il parametro di Enumè qualsiasi tipo che soddisfi

 E extends Enum<E>

Maurice Naftalin (coautore, Java Generics and Collections)


1
@newacct OK, ora capisco: vorresti che tutti gli enum fossero istanze di Enum <E>, giusto? (Perché se sono istanze di un sottotipo Enum, si applica l'argomento sopra.) Ma allora non avrai più enumerazioni sicure di tipo, quindi perdi il punto di avere enumerazioni.
Maurice Naftalin,

1
@newacct Non vuoi insistere sul fatto che Seasonimplementa Comparable<Season>?
Maurice Naftalin,

2
@newacct Guarda la definizione di Enum. Per confrontare un'istanza con un'altra, deve confrontare i loro ordinali. Quindi l'argomento del compareTometodo deve essere stato dichiarato come Enumsottotipo, altrimenti il ​​compilatore dirà (correttamente) che non ha un ordinale.
Maurice Naftalin,

2
@MauriceNaftalin: Se Java non proibisse la sottoclasse manuale Enum, sarebbe possibile farlo class OneEnum extends Enum<AnotherEnum>{}, anche con come Enumviene dichiarato in questo momento. Non avrebbe molto senso per essere in grado di confrontare un tipo di enum con un altro, e allora Enum's compareTonon avrebbe senso come dichiarato in ogni caso. I limiti non forniscono alcun aiuto a questo.
newacct,

2
@MauriceNaftalin: se l'ordinale fosse la ragione, public class Enum<E extends Enum<?>>sarebbe anche sufficiente.
newacct,

6

Ciò può essere illustrato da un semplice esempio e da una tecnica che può essere utilizzata per implementare chiamate di metodi concatenate per le sottoclassi. Nell'esempio seguente viene setNamerestituito un Nodeconcatenamento che non funziona 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 Cityora 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!
    }
}

La tua soluzione ha un cast non controllato: return (CHILD) this;considera l'aggiunta di un metodo getThis (): protected CHILD getThis() { return this; } vedi: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

@Roland grazie per un link, ne ho preso in prestito un'idea. Potresti spiegarmi o indirizzare a un articolo che spiega perché questa è una cattiva pratica in questo caso particolare? Il metodo nel collegamento richiede una maggiore digitazione e questo è l'argomento principale per cui lo sto evitando. Non ho mai visto errori di cast in questo caso + So che ci sono alcuni inevitabili errori di cast - vale a dire quando uno memorizza oggetti di più tipi in una stessa raccolta. Quindi, se i cast non controllati non sono critici e il design è in qualche modo complesso ( Node<T>non è il caso), li sto ignorando per risparmiare tempo.
Andrey Chaschev,

La tua modifica non è così diversa da prima oltre ad aggiungere un po 'di zucchero sintattico, considera che il seguente codice verrà effettivamente compilato ma genererà un errore di runtime: `Nodo <Città> nodo = nuovo Nodo <Città> () .setName (" nodo "). setSquare (1); `Se guardi il codice byte java vedrai che a causa della cancellazione del tipo l'istruzione return (SELF) this;viene compilata return this;, quindi puoi semplicemente lasciarla fuori.
Roland,

@Roland grazie, questo è quello che mi serviva - aggiornerò l'esempio quando sarò libero.
Andrey Chaschev,

Anche il seguente link è buono: angelikalanger.com/GenericsFAQ/FAQSections/…
Roland

3

Non sei il solo a chiederti cosa significhi; vedi il blog di Chaotic Java .

"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".


1

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.

Supponiamo 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(){...}
}

Mi permette ancora di creare un film in class Foo extends Node<City>cui Foo non è correlato a City.
nuovo

1
Sicuro, ed è sbagliato? Io non la penso così. Il contratto di base fornito da Node <City> è ancora onorato, solo la tua sottoclasse Foo è meno utile da quando inizi a lavorare con Foos ma fai uscire le Città dall'ADT. Potrebbe esserci un caso d'uso per questo, ma molto probabilmente più semplice e utile per rendere il parametro generico uguale alla sottoclasse. Ma in entrambi i casi, il designer ha questa scelta.
mdma,

@mdma: sono d'accordo. Quindi, a che serve il limite, oltre al solo class Node<T>?
newacct

1
@nozebacle: il tuo esempio non dimostra che "questa particolare struttura è necessaria". class Node<T>è pienamente coerente con il tuo esempio.
newacct

1

Se guardi il Enumcodice sorgente, 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);
    } 
}

Prima cosa, cosa E extends Enum<E>significa? 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, è tradotto in

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

Ciò 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 il getDeclaringClass()cast per l' Class<T>oggetto corretto .

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


Nulla di ciò che hai mostrato compareToo getDeclaringClassrichiede il extends Enum<E>limite.
newacct

0

Secondo Wikipedia, questo modello è chiamato modello di modello curiosamente ricorrente . Fondamentalmente, usando il modello CRTP, possiamo facilmente fare riferimento al tipo di sottoclasse senza il tipo casting, il che significa che usando il modello, possiamo imitare la funzione virtuale.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.