Domanda

Ho un gran numero di enumerazioni che implementano questa interfaccia:

/**
 * Interface for an enumeration, each element of which can be uniquely identified by it's code
 */
public interface CodableEnum {

    /**
     * Get the element with a particular code
     * @param code
     * @return
     */
    public CodableEnum getByCode(String code);

    /**
     * Get the code that identifies an element of the enum
     * @return
     */
    public String getCode();
}

Un tipico esempio è:

public enum IMType implements CodableEnum {

    MSN_MESSENGER("msn_messenger"),
    GOOGLE_TALK("google_talk"),
    SKYPE("skype"),
    YAHOO_MESSENGER("yahoo_messenger");

    private final String code;

    IMType (String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   

    public IMType getByCode(String code) {
        for (IMType e : IMType.values()) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
    }
}

Come puoi immaginare, questi metodi sono praticamente identici in tutte le implementazioni di CodableEnum.Vorrei eliminare questa duplicazione, ma francamente non so come.Ho provato a utilizzare una classe come la seguente:

public abstract class DefaultCodableEnum implements CodableEnum {

    private final String code;

    DefaultCodableEnum(String code) {
        this.code = code;
    }

    public String getCode() {
        return this.code;
    }   

    public abstract CodableEnum getByCode(String code);  
}

Ma questo risulta essere abbastanza inutile perché:

  1. Un'enumerazione non può estendere una classe
  2. Gli elementi di un'enumerazione (SKYPE, GOOGLE_TALK e così via) non possono estendere una classe
  3. Non posso fornire un'implementazione predefinita di getByCode(), perché DefaultCodableEnum non è di per sé un Enum.Ho provato a modificare DefaultCodableEnum per estendere java.lang.Enum, ma questo non sembra essere consentito.

Qualche suggerimento che non si basi sulla riflessione?Grazie, Don

È stato utile?

Soluzione

Potresti fattorizzare il codice duplicato in a CodeableEnumHelper classe:

public class CodeableEnumHelper {
    public static CodeableEnum getByCode(String code, CodeableEnum[] values) {
        for (CodeableEnum e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

Ogni CodeableEnum la classe dovrebbe comunque implementare a getByCode metodo, ma l’effettiva attuazione del metodo è stata almeno centralizzata in un unico luogo.

public enum IMType implements CodeableEnum {
    ...
    public IMType getByCode(String code) {
        return (IMType)CodeableEnumHelper.getByCode(code, this.values());
    } 
}

Altri suggerimenti

Le enumerazioni astratte sono potenzialmente molto utili (e attualmente non consentite).Ma esiste una proposta e un prototipo se desideri fare pressione su qualcuno in Sun per aggiungerlo:

http://freddy33.blogspot.com/2007/11/abstract-enum-ricky-carlson-way.html

Sole RFE:

http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6570766

Per riordinare il codice di Dave:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> E getByCode(
        String code, E[] values
    ) {
        for (E e : values) {
            if (e.getCode().equalsIgnoreCase(code)) {
                return e;
            }
        }
        return null;
    }
}

public enum IMType implements CodableEnum {
    ...
    public IMType getByCode(String code) {
        return CodeableEnumHelper.getByCode(code, values());
    } 
}

O in modo più efficiente:

public class CodeableEnumHelper {
    public static <E extends CodeableEnum> Map<String,E> mapByCode(
        E[] values
    ) {
        Map<String,E> map = new HashMap<String,E>();
        for (E e : values) {
            map.put(e.getCode().toLowerCase(Locale.ROOT), value) {
        }
        return map;
    }
}

public enum IMType implements CodableEnum {
    ...
    private static final Map<String,IMType> byCode =
        CodeableEnumHelper.mapByCode(values());
    public IMType getByCode(String code) {
        return byCode.get(code.toLowerCase(Locale.ROOT));
    } 
}

Ho avuto un problema simile con un componente di localizzazione che ho scritto.Il mio componente è progettato per accedere ai messaggi localizzati con costanti enumerate che indicizzano in un pacchetto di risorse, non è un problema difficile.

Ho scoperto che stavo copiando e incollando lo stesso codice enum "modello" ovunque.La mia soluzione per evitare la duplicazione è un generatore di codice che accetta un file di configurazione XML con i nomi costanti di enumerazione e gli argomenti del costruttore.L'output è il codice sorgente Java con i comportamenti "duplicati".

Ora mantengo i file di configurazione e il generatore, non tutto il codice duplicato.Ovunque avrei avuto il codice sorgente enum, ora c'è un file di configurazione XML.I miei script di build rilevano i file generati non aggiornati e richiamano il generatore di codice per creare il codice enum.

Puoi vedere questo componente Qui.Viene preso in considerazione il modello che stavo copiando e incollando un foglio di stile XSLT.IL generatore di codici esegue la trasformazione del foglio di stile.UN file di input è abbastanza conciso rispetto al codice sorgente enum generato.

HTH,
Greg

Sfortunatamente, non penso che ci sia un modo per farlo.La soluzione migliore sarebbe probabilmente quella di rinunciare del tutto alle emum e utilizzare l'estensione di classe convenzionale e i membri statici.Altrimenti, abituati a duplicare quel codice.Scusa.

Crea una classe di utilità indipendente dai tipi che caricherà le enumerazioni in base al codice:

L'interfaccia si riduce a:

public interface CodeableEnum {
    String getCode();
}

La classe di utilità è:

import java.lang.reflect.InvocationTargetException;


public class CodeableEnumUtils {
    @SuppressWarnings("unchecked")
    public static <T extends CodeableEnum>  T getByCode(String code, Class<T> enumClass) throws IllegalArgumentException, SecurityException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
        T[] allValues = (T[]) enumClass.getMethod("values", new Class[0]).invoke(null, new Object[0]);
        for (T value : allValues) {
            if (value.getCode().equals(code)) {
                return value;
            }
        }
        return null;
}

}

Un caso di test che dimostra l'utilizzo:

import junit.framework.TestCase;


public class CodeableEnumUtilsTest extends TestCase {
    public void testWorks() throws Exception {
    assertEquals(A.ONE, CodeableEnumUtils.getByCode("one", A.class));
      assertEquals(null, CodeableEnumUtils.getByCode("blah", A.class));
    }

enum A implements CodeableEnum {
    ONE("one"), TWO("two"), THREE("three");

    private String code;

    private A(String code) {
        this.code = code;
    }

    public String getCode() {
        return code;
    }   
}
}

Ora stai solo duplicando il metodo getCode() e il metodo getByCode() è in un unico posto.Potrebbe essere carino racchiudere anche tutte le eccezioni in un'unica RuntimeException :)

Qui ho un'altra soluzione:

interface EnumTypeIF {
String getValue();

EnumTypeIF fromValue(final String theValue);

EnumTypeIF[] getValues();

class FromValue {
  private FromValue() {
  }

  public static EnumTypeIF valueOf(final String theValue, EnumTypeIF theEnumClass) {

    for (EnumTypeIF c : theEnumClass.getValues()) {
      if (c.getValue().equals(theValue)) {
        return c;
      }
    }
    throw new IllegalArgumentException(theValue);
  }
}

Il trucco è che la classe interna può essere utilizzata per contenere "metodi globali".

Ha funzionato abbastanza bene per me.OK, devi implementare 3 metodi, ma questi metodi sono solo delegatori.

Sembra che tu stia effettivamente implementando le informazioni sul tipo di runtime.Java lo fornisce come funzionalità del linguaggio.

Ti suggerisco di cercare RTTI o riflessione.

Non penso che questo sia possibile.Tuttavia, potresti utilizzare il metodo valueOf(String name) dell'enum se intendi utilizzare il nome del valore enum come codice.

Che ne dici di un metodo generico statico?Puoi riutilizzarlo dai metodi getByCode() della tua enumerazione o semplicemente usarlo direttamente.Utilizzo sempre ID interi per le mie enumerazioni, quindi il mio metodo getById() deve fare solo questo:valori restituiti()[id].È molto più veloce e più semplice.

Se vuoi davvero un'eredità, non dimenticare che puoi implementa tu stesso il modello enum, come nel cattivo vecchio Java 1.4 giorni.

Il più vicino possibile a ciò che volevi era creare un modello in IntelliJ che "implementasse" il codice generico (usando valueOf(String name) di enum).Non perfetto ma funziona abbastanza bene.

Nel tuo caso specifico, i metodi getCode() / getByCode(String code) sembrano molto chiusi (eufemisticamente parlando) al comportamento dei metodi toString() / valueOf(String value) forniti da tutte le enumerazioni.Perché non vuoi usarli?

Un'altra soluzione sarebbe quella di non inserire nulla nell'enumerazione stessa e fornire semplicemente una mappa bidirezionale Enum <-> Codice per ciascuna enumerazione.Potresti ad es.utilizzo BiMap immutabile da Google Collections per questo.

In questo modo non esiste alcun codice duplicato.

Esempio:

public enum MYENUM{
  VAL1,VAL2,VAL3;
}

/** Map MYENUM to its ID */
public static final ImmutableBiMap<MYENUM, Integer> MYENUM_TO_ID = 
new ImmutableBiMap.Builder<MYENUM, Integer>().
put(MYENUM.VAL1, 1).
put(MYENUM.VAL2, 2).
put(MYENUM.VAL3, 3).
build();

Secondo me, questo sarebbe il modo più semplice, senza riflessioni e senza aggiungere alcun wrapper aggiuntivo alla tua enumerazione.

Crei un'interfaccia implementata dalla tua enumerazione:

public interface EnumWithId {

    public int getId();

}

Quindi in una classe helper crei semplicemente un metodo come questo:

public <T extends EnumWithId> T getById(Class<T> enumClass, int id) {
    T[] values = enumClass.getEnumConstants();
    if (values != null) {
        for (T enumConst : values) {
            if (enumConst.getId() == id) {
                return enumConst;
            }
        }
    }

    return null;
}

Questo metodo potrebbe quindi essere utilizzato in questo modo:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top