Perché i letterali di enum Java non dovrebbero avere parametri di tipo generico?


148

Gli enumerati Java sono fantastici. Lo sono anche i generici. Naturalmente conosciamo tutti i limiti di quest'ultimo a causa della cancellazione del tipo. Ma c'è una cosa che non capisco, perché non posso creare un enum come questo:

public enum MyEnum<T> {
    LITERAL1<String>,
    LITERAL2<Integer>,
    LITERAL3<Object>;
}

Questo parametro di tipo generico <T>a sua volta potrebbe quindi essere utile in vari punti. Immagina un parametro di tipo generico in un metodo:

public <T> T getValue(MyEnum<T> param);

O anche nella stessa classe enum:

public T convert(Object o);

Esempio più concreto n. 1

Poiché l'esempio sopra potrebbe sembrare troppo astratto per alcuni, ecco un esempio più reale del perché voglio farlo. In questo esempio voglio usare

  • Enum, perché poi posso enumerare un set finito di chiavi di proprietà
  • Generici, perché allora posso avere una sicurezza del tipo a livello di metodo per la memorizzazione delle proprietà
public interface MyProperties {
     public <T> void put(MyEnum<T> key, T value);
     public <T> T get(MyEnum<T> key);
}

Esempio più concreto n. 2

Ho un elenco di tipi di dati:

public interface DataType<T> {}

public enum SQLDataType<T> implements DataType<T> {
    TINYINT<Byte>,
    SMALLINT<Short>,
    INT<Integer>,
    BIGINT<Long>,
    CLOB<String>,
    VARCHAR<String>,
    ...
}

Ogni enum letterale avrebbe ovviamente proprietà aggiuntive basate sul tipo generico <T>, mentre allo stesso tempo sarebbe un enum (immutabile, singleton, enumerabile, ecc. Ecc.)

Domanda:

Nessuno ci ha pensato? Si tratta di una limitazione relativa al compilatore? Considerando il fatto che la parola chiave " enum " è implementata come zucchero sintattico, che rappresenta il codice generato per la JVM, non capisco questa limitazione.

Chi può spiegarmelo? Prima di rispondere, considera questo:

  • So che i tipi generici vengono cancellati :-)
  • So che ci sono soluzioni alternative usando gli oggetti Class. Sono soluzioni alternative.
  • I tipi generici generano cast di tipi generati dal compilatore ove applicabile (ad es. Quando si chiama il metodo convert ()
  • Il tipo generico <T> sarebbe sull'enum. Quindi è vincolato da ciascuno dei letterali dell'enum. Quindi il compilatore saprebbe quale tipo applicare per scrivere qualcosa di simileString string = LITERAL1.convert(myObject); Integer integer = LITERAL2.convert(myObject);
  • Lo stesso vale per il parametro di tipo generico nel T getvalue()metodo. Il compilatore può applicare il cast di tipo durante la chiamataString string = someClass.getValue(LITERAL1)

3
Non capisco nemmeno questa limitazione. Mi sono imbattuto di recente in questo caso in cui il mio enum conteneva diversi tipi "Comparabili" e, con generici, solo i tipi Comparabili degli stessi tipi possono essere confrontati senza avvertimenti che devono essere soppressi (anche se in fase di esecuzione si confronterebbero quelli corretti). Avrei potuto sbarazzarmi di questi avvertimenti usando un tipo associato nell'enum per specificare quale tipo comparabile era supportato, ma invece ho dovuto aggiungere l'annotazione SuppressWarnings - assolutamente no! Dal momento che compareTo lancia comunque un'eccezione di cast di classe, va bene immagino, ma comunque ...
MetroidFan2002

5
(+1) Sono nel mezzo del tentativo di colmare una lacuna di sicurezza nel mio progetto, fermata da questa limitazione arbitraria. Considera questo: trasforma il enumlinguaggio in un linguaggio "typesafe enum" che abbiamo usato prima di Java 1.5. All'improvviso, puoi far parametrizzare i tuoi membri enum. Questo è probabilmente quello che farò ora.
Marko Topolnik,

1
@EdwinDalorzo: aggiornata la domanda con un esempio concreto di jOOQ , dove questo sarebbe stato immensamente utile in passato.
Lukas Eder,

2
@LukasEder Adesso vedo il tuo punto. Sembra una nuova funzionalità. Forse dovresti suggerirlo nella mailing list del progetto Coin vedo altre interessanti proposte lì su enum, ma non una come la tua.
Edwin Dalorzo

1
Completamente d'accordo. Enum senza generici sono paralizzati. Anche il tuo caso n. 1 è mio. Se ho bisogno di enum generico, rinuncio a JDK5 e lo implemento in un semplice vecchio stile Java 1.4. Questo approccio ha anche un ulteriore vantaggio : non sono costretto ad avere tutte le costanti in una classe o anche in un pacchetto. Pertanto, lo stile pacchetto per funzionalità è realizzato molto meglio. Questo è perfetto solo per "enum" simili a configurazioni - le costanti sono distribuite in pacchetti in base al loro significato logico (e se desidero vederle tutte, ho mostrato il tipo hieararchy).
Tomáš Záluský

Risposte:


50

Questo è ora in discussione a partire da Enumer Enhanced JEP-301 . L'esempio fornito nel JEP è, che è esattamente quello che stavo cercando:

enum Argument<X> { // declares generic enum
   STRING<String>(String.class), 
   INTEGER<Integer>(Integer.class), ... ;

   Class<X> clazz;

   Argument(Class<X> clazz) { this.clazz = clazz; }

   Class<X> getClazz() { return clazz; }
}

Class<String> cs = Argument.STRING.getClazz(); //uses sharper typing of enum constant

Sfortunatamente, il JEP è ancora alle prese con problemi significativi: http://mail.openjdk.java.net/pipermail/amber-spec-experts/2017-May/000041.html


2
A dicembre 2018, c'erano di nuovo alcuni segni di vita intorno a 301 JEP , ma sfuggire a questa discussione chiarisce che i problemi sono ancora lontani dall'essere risolti.
Pont

11

La risposta è nella domanda:

a causa della cancellazione del tipo

Nessuno di questi due metodi è possibile, poiché il tipo di argomento viene cancellato.

public <T> T getValue(MyEnum<T> param);
public T convert(Object);

Per realizzare quei metodi potresti comunque costruire il tuo enum come:

public enum MyEnum {
    LITERAL1(String.class),
    LITERAL2(Integer.class),
    LITERAL3(Object.class);

    private Class<?> clazz;

    private MyEnum(Class<?> clazz) {
      this.clazz = clazz;
    }

    ...

}

2
Bene, la cancellazione avviene al momento della compilazione. Ma il compilatore può utilizzare informazioni di tipo generico per i controlli del tipo. E poi "converte" il tipo generico in type cast. Riformulerò la domanda
Lukas Eder il

2
Non sono sicuro che seguirò del tutto. Prendi public T convert(Object);. Immagino che questo metodo potrebbe ad esempio restringere un gruppo di tipi diversi a <T>, che ad esempio è una stringa. La costruzione dell'oggetto String è runtime - niente che il compilatore fa. Quindi è necessario conoscere il tipo di runtime, ad esempio String.class. Oppure mi sfugge qualcosa?
Martin Algesten,

6
Penso che ti manchi il fatto che il tipo generico <T> sia nell'enum (o nella sua classe generata). Le uniche istanze dell'enum sono i suoi letterali, che forniscono tutti una costante associazione di tipo generico. Quindi non vi è alcuna ambiguità riguardo a T in T convertito pubblico (Object);
Lukas Eder,

2
Aha! Fatto. Sei più intelligente di me :)
Martin Algesten,

1
Immagino che si tratti di un enum che viene valutato in modo simile a tutto il resto. In Java, anche se le cose sembrano costanti, non lo sono. Considerare public final static String FOO;con un blocco statico static { FOO = "bar"; }: anche le costanti vengono valutate anche se note al momento della compilazione.
Martin Algesten,

5

Perché non puoi. Sul serio. Ciò potrebbe essere aggiunto alle specifiche della lingua. Non è stato Aggiungerebbe una certa complessità. Tale vantaggio per i costi significa che non è una priorità assoluta.

Aggiornamento: attualmente aggiunto alla lingua in JEP 301: Enhanced Enums .


Potresti approfondire le complicazioni?
Mr_and_Mrs_D

4
Ci sto pensando da un paio di giorni e non vedo ancora complicazioni.
Marko Topolnik,

4

Ci sono altri metodi in ENUM che non funzionerebbero. Cosa sarebbe MyEnum.values()tornato?

Che dire MyEnum.valueOf(String name)?

Per il valoreSe pensi che quel compilatore possa rendere simile un metodo generico

valore pubblico MyEnum staticoOf (nome stringa);

per chiamarlo così MyEnum<String> myStringEnum = MyEnum.value("some string property"), neanche quello avrebbe funzionato. Ad esempio, se chiami MyEnum<Int> myIntEnum = MyEnum.<Int>value("some string property")? Non è possibile implementare quel metodo per funzionare correttamente, ad esempio per generare un'eccezione o restituire null quando lo si chiama come a MyEnum.<Int>value("some double property")causa della cancellazione del tipo.


2
Perché non dovrebbero funzionare? Userebbero semplicemente i caratteri jolly ...: MyEnum<?>[] values()eMyEnum<?> valueOf(...)
Lukas Eder l'

1
Ma poi non puoi fare questo compito MyEnum<Int> blabla = valueOf("some double property");perché i tipi non sono compatibili. Inoltre, in questo caso si desidera ottenere null perché si desidera restituire MyEnum <Int> che non esiste per il doppio nome della proprietà e non è possibile far funzionare correttamente quel metodo a causa della cancellazione.
user1944408

Inoltre, se esegui il ciclo attraverso i valori (), dovrai utilizzare MyEnum <?> Che non è quello che desideri di solito perché, ad esempio, non puoi eseguire il ciclo solo attraverso le proprietà Int. Inoltre avresti bisogno di fare un sacco di casting che vuoi evitare, suggerirei di creare enumerazioni diverse per ogni tipo o creare la tua classe con istanze ...
user1944408

Beh, immagino che non puoi avere entrambi. Di solito, ricorro alle mie implementazioni "enum". È solo che Enumha molte altre utili funzioni, e anche questa sarebbe facoltativa e molto utile ...
Lukas Eder,

0

Francamente questa sembra più una soluzione alla ricerca di un problema che altro.

L'intero scopo dell'enumerazione Java è di modellare un'enumerazione di istanze di tipo che condividono proprietà simili in modo da fornire coerenza e ricchezza oltre a quelle di rappresentazioni String o Integer comparabili.

Prendi un esempio di enum di un libro di testo. Questo non è molto utile o coerente:

public enum Planet<T>{
    Earth<Planet>,
    Venus<String>,
    Mars<Long>
    ...etc.
}

Perché dovrei desiderare che i miei diversi pianeti abbiano conversioni di tipo generico diverso? Che problema risolve? Giustifica la complicazione della semantica linguistica? Se ho bisogno di questo comportamento è un enum lo strumento migliore per raggiungerlo?

Inoltre come gestiresti conversioni complesse?

per esempio

public enum BadIdea<T>{
   INSTANCE1<Long>,
   INSTANCE2<MyComplexClass>;
}

È abbastanza facile String Integerfornire il nome o l'ordinale. Ma i generici ti permetterebbero di fornire qualsiasi tipo. Come gestiresti la conversione MyComplexClass? Ora fai confondere due costrutti costringendo il compilatore a sapere che ci sono un sottoinsieme limitato di tipi che possono essere forniti a enum generici e introducendo ulteriore confusione nel concetto (Generics) che sembra già sfuggire a molti programmatori.


17
Pensare a un paio di esempi in cui non sarebbe utile è un argomento terribile che non sarebbe mai utile.
Elias Vasylenko,

1
Gli esempi supportano il punto. Le istanze enum sono sottoclassi del tipo (simpatiche e semplici) e che includendo i generici è una lattina di vermi in termini di complessità per un beneficio molto oscuro. Se hai intenzione di sottovalutarmi, dovresti anche sottovalutare Tom Hawtin che ha detto la stessa cosa in non così tante parole
nsfyn55

1
@ nsfyn55 Gli enum sono una delle funzioni linguistiche più complesse e magiche di Java. Non esiste un'unica funzione in un'altra lingua che genera automaticamente metodi statici, ad esempio. Inoltre, ogni tipo di enum è già un'istanza di un tipo generico.
Marko Topolnik,

1
@ nsfyn55 L'obiettivo del progetto era rendere i membri di enum potenti e flessibili, motivo per cui supportano funzionalità avanzate come metodi di istanza personalizzati e persino variabili di istanze mutabili. Sono progettati per avere un comportamento specializzato per ciascun membro individualmente in modo da poter partecipare come collaboratori attivi in ​​scenari di utilizzo complessi. Soprattutto, l'enum Java è stato progettato per rendere sicuro il tipo di linguaggio di enumerazione generale , ma la mancanza di parametri di tipo sfortunatamente lo rende al di sotto dell'obiettivo più caro. Sono abbastanza convinto che ci fosse una ragione molto specifica per questo.
Marko Topolnik,

1
Non ho notato la tua menzione di contraddizione ... Java lo supporta, solo il suo sito di utilizzo, il che lo rende un po 'ingombrante. interface Converter<IN, OUT> { OUT convert(IN in); } <E> Set<E> convertListToSet(List<E> in, Converter<? super List<E>, ? extends Set<E>> converter) { return converter.convert(in); }Dobbiamo elaborare manualmente ogni volta che tipo viene consumato e quale prodotto e specificare i limiti di conseguenza.
Marko Topolnik,

-2

Perché "enum" è l'abbreviazione di enumerazione. È solo un insieme di costanti nominate che sostituiscono i numeri ordinali per rendere il codice più leggibile.

Non vedo quale potrebbe essere il significato previsto di una costante con parametri di tipo.


1
In java.lang.String: public static final Comparator<String> CASE_INSENSITIVE_ORDER. Adesso vedi? :-)
Lukas Eder,

4
Hai detto di non vedere quale potrebbe essere il significato di una costante con parametri. Quindi ti ho mostrato una costante con parametri di tipo (non un puntatore a funzione).
Lukas Eder,

Certo che no, poiché il linguaggio java non conosce una cosa come un puntatore a funzione. Tuttavia, non la costante è di tipo parametrizzato, ma è di tipo. E il parametro type stesso è costante, non una variabile di tipo.
Ingo,

Ma la sintassi dell'enum è solo zucchero sintattico. Sotto, sono esattamente come CASE_INSENSITIVE_ORDER... Se Comparator<T>fosse un enum, perché non avere letterali con nessun associato associato <T>?
Lukas Eder,

Se Comparator <T> fosse un enum, allora davvero potremmo avere ogni sorta di cose strane. Forse qualcosa come Integer <T>. Ma non lo è.
Ingo,

-3

Penso perché fondamentalmente Enums non può essere istanziato

Dove imposteresti la classe T, se JVM ti permettesse di farlo?

L'enumerazione è un dato che dovrebbe essere sempre lo stesso, o almeno, che non cambierà dinamicamente.

nuovo MyEnum <> ()?

Tuttavia, può essere utile il seguente approccio

public enum MyEnum{

    LITERAL1("s"),
    LITERAL2("a"),
    LITERAL3(2);

    private Object o;

    private MyEnum(Object o) {
        this.o = o;
    }

    public Object getO() {
        return o;
    }

    public void setO(Object o) {
        this.o = o;
    }   
}

8
Non sono sicuro che mi piace il setO()metodo su un enum. Considero costanti di enum e per me ciò implica immutabile . Quindi, anche se fosse possibile, non lo farei.
Martin Algesten,

2
Gli enum sono istanziati nel codice generato dal compilatore. Ogni letterale viene effettivamente creato chiamando i costruttori privati. Quindi, ci sarebbe la possibilità di passare il tipo generico al costruttore in fase di compilazione.
Lukas Eder,

2
I setter su enums sono completamente normali. Soprattutto se stai usando l'enum per creare un singleton (Item 3 Effective Java di Joshua Bloch)
Preston,
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.