Come implementare enum con i generici?

Ho un’interfaccia generica come questa:

interface A { T getValue(); } 

Questa interfaccia ha istanze limitate, quindi sarebbe meglio implementarle come valori enum. Il problema è che queste istanze hanno diversi tipi di valori, quindi ho provato il seguente approccio ma non viene compilato:

 public enum B implements A { A1 { @Override public String getValue() { return "value"; } }, A2 { @Override public Integer getValue() { return 0; } }; } 

Qualche idea su questo?

Non puoi Java non consente tipi generici sulle costanti enum. Sono ammessi sui tipi di enumerazione, tuttavia:

 public enum B implements A { A1, A2; } 

Quello che potresti fare in questo caso è avere un tipo di enumerazione per ogni tipo generico, o “falso” avere un enumerismo semplicemente facendolo diventare una class:

 public class B implements A { public static final B A1 = new B(); public static final B A2 = new B(); private B() {}; } 

Sfortunatamente, entrambi hanno degli svantaggi.

Come sviluppatori Java che progettano determinate API, ci imbattiamo spesso in questo problema. Stavo riconfermando i miei dubbi quando mi sono imbattuto in questo post, ma ho una soluzione verbosa ad esso:

 // class name is awful for this example, but it will make more sense if you // read further public interface MetaDataKey extends Serializable { T getValue(); } public final class TypeSafeKeys { static enum StringKeys implements MetaDataKey { A1("key1"); private final String value; StringKeys(String value) { this.value = value; } @Override public String getValue() { return value; } } static enum IntegerKeys implements MetaDataKey { A2(0); private final Integer value; IntegerKeys (Integer value) { this.value = value; } @Override public Integer getValue() { return value; } } public static final MetaDataKey A1 = StringKeys.A1; public static final MetaDataKey A2 = IntegerKeys.A2; } 

A quel punto, si ottiene il vantaggio di essere un valore di enum zione veramente costante (e tutti i vantaggi che ne derivano), oltre ad un’implementazione unica interface , ma si ha l’accessibilità globale desiderata enum .

Chiaramente, questo aggiunge verbosità, che crea il potenziale per errori di copia / incolla. Potresti rendere public l’ enum e semplicemente aggiungere un ulteriore livello al loro accesso.

I progetti che tendono ad utilizzare queste caratteristiche tendono a soffrire di fragili implementazioni equals perché solitamente sono accoppiati con qualche altro valore univoco, come un nome, che può essere duplicato involontariamente attraverso il codebase per uno scopo simile, ma diverso. Usando enum su tutta la linea, l’uguaglianza è un omaggio che è immune a un comportamento così fragile.

Il principale svantaggio di un sistema, oltre alla verbosità, è l’idea di convertire avanti e indietro tra le chiavi univoche globali (ad es., Il marshalling da e verso JSON). Se sono solo delle chiavi, possono essere reintegrate in modo sicuro (duplicate) al costo di sprecare memoria, ma usare quello che prima era un punto debole– equals un vantaggio.

C’è una soluzione a questo che fornisce l’unicità di implementazione globale ingombrandolo con un tipo anonimo per istanza globale:

 public abstract class BasicMetaDataKey implements MetaDataKey { private final T value; public BasicMetaDataKey(T value) { this.value = value; } @Override public T getValue() { return value; } // @Override equals // @Override hashCode } public final class TypeSafeKeys { public static final MetaDataKey A1 = new BasicMetaDataKey("value") {}; public static final MetaDataKey A2 = new BasicMetaDataKey(0) {}; } 

Nota che ogni istanza utilizza un’implementazione anonima, ma non è necessario nient’altro per implementarlo, quindi {} sono vuoti. Ciò è sia confuso che fastidioso, ma funziona se i riferimenti di istanza sono preferibili e il disordine è ridotto al minimo, sebbene possa essere un po ‘criptico per gli sviluppatori Java meno esperti, rendendo così più difficile da mantenere.

Infine, l’unico modo per fornire unicità e riassegnazione globale è essere un po ‘più creativi con ciò che sta accadendo. L’uso più comune per le interfacce condivise a livello globale che ho visto sono per i bucket MetaData che tendono a mescolare molti valori diversi, con tipi diversi (il T , su base per chiave):

 public interface MetaDataKey extends Serializable { Class getType(); String getName(); } public final class TypeSafeKeys { public static enum StringKeys implements MetaDataKey { A1; @Override public Class getType() { return String.class; } @Override public String getName() { return getDeclaringClass().getName() + "." + name(); } } public static enum IntegerKeys implements MetaDataKey { A2; @Override public Class getType() { return Integer.class; } @Override public String getName() { return getDeclaringClass().getName() + "." + name(); } } public static final MetaDataKey A1 = StringKeys.A1; public static final MetaDataKey A2 = IntegerKeys.A2; } 

Ciò fornisce la stessa flessibilità della prima opzione e fornisce un meccanismo per ottenere un riferimento tramite riflessione, se diventa necessario in seguito, evitando quindi la necessità di essere immediatamente attendibili. Evita inoltre molti errori di copia / incolla inclini all’errore che la prima opzione fornisce perché non verrà compilato se il primo metodo è sbagliato e il secondo metodo non ha bisogno di modifiche. L’unica nota è che dovresti assicurarti che l’ enum destinata ad essere usata in quel modo sia public per evitare che qualcuno riceva errori di accesso perché non hanno accesso enum interiore; se non si desidera che quei MetaDataKey attraversando un cavo di marshalling, è ansible utilizzarli per mantenerli nascosti da pacchetti esterni per scartarli automaticamente (durante il marshalling, verificare in modo riflessivo per vedere se l’ enum è accessibile e, in caso contrario, quindi ignorare la chiave / il valore). Non c’è nulla da guadagnare o da perdere rendendolo public tranne che fornire due modi per accedere all’istanza, se vengono mantenuti static riferimenti static più ovvi (poiché le istanze enum sono comunque).

Spero solo che abbiano fatto in modo che enum s potesse estendere gli oggetti in Java. Forse in Java 9?

L’opzione finale in realtà non risolve il tuo bisogno, come stavi chiedendo valori, ma ho il sospetto che questo arrivi all’objective reale.

Se JEP 301: Enhanced Enum viene accettato, allora sarai in grado di usare la syntax come questa (prendi dalla proposta):

 enum Primitive { INT(Integer.class, 0) { int mod(int x, int y) { return x % y; } int add(int x, int y) { return x + y; } }, FLOAT(Float.class, 0f) { long add(long x, long y) { return x + y; } }, ... ; final Class boxClass; final X defaultValue; Primitive(Class boxClass, X defaultValue) { this.boxClass = boxClass; this.defaultValue = defaultValue; } }