Pregunta

Tengo una gran cantidad de enumeraciones que implementan esta interfaz:

/**
 * 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 ejemplo típico es:

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

Como puede imaginar, estos métodos son prácticamente idénticos en todas las implementaciones de CodableEnum.Me gustaría eliminar esta duplicación, pero francamente no sé cómo.Intenté usar una clase como la siguiente:

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

Pero esto resulta bastante inútil porque:

  1. Una enumeración no puede extender una clase
  2. Los elementos de una enumeración (SKYPE, GOOGLE_TALK, etc.) no pueden extender una clase
  3. No puedo proporcionar una implementación predeterminada de getByCode(), porque DefaultCodableEnum no es en sí mismo un Enum.Intenté cambiar DefaultCodableEnum para extender java.lang.Enum, pero parece que esto no está permitido.

¿Alguna sugerencia que no se base en la reflexión?Gracias Don

¿Fue útil?

Solución

Podrías factorizar el código duplicado en un CodeableEnumHelper clase:

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

Cada CodeableEnum La clase todavía tendría que implementar un getByCode método, pero la implementación real del método al menos se ha centralizado en un solo lugar.

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

Otros consejos

Las enumeraciones abstractas son potencialmente muy útiles (y actualmente no están permitidas).Pero existe una propuesta y un prototipo si desea presionar a alguien en Sun para que los agregue:

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

Sol RFE:

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

Para ordenar el código de 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 más eficientemente:

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

Tuve un problema similar con un componente de localización que escribí.Mi componente está diseñado para acceder a mensajes localizados con constantes de enumeración que indexan en un paquete de recursos, lo que no es un problema difícil.

Descubrí que estaba copiando y pegando el mismo código de enumeración de "plantilla" por todas partes.Mi solución para evitar la duplicación es un generador de código que acepte un archivo de configuración XML con los nombres constantes de enumeración y los argumentos del constructor.El resultado es el código fuente de Java con los comportamientos "duplicados".

Ahora mantengo los archivos de configuración y el generador, no todo el código duplicado.En todos los lugares donde habría tenido el código fuente de enumeración, ahora hay un archivo de configuración XML.Mis scripts de compilación detectan archivos generados desactualizados e invocan el generador de código para crear el código de enumeración.

Puedes ver este componente. aquí.La plantilla que estaba copiando y pegando se incluye en una hoja de estilo XSLT.El generador de códigos ejecuta la transformación de la hoja de estilo.Un fichero de entrada Es bastante conciso en comparación con el código fuente de enumeración generado.

HTH,
Greg

Desafortunadamente, no creo que haya una manera de hacer esto.Su mejor opción probablemente sería renunciar por completo a los emums y utilizar la extensión de clase convencional y miembros estáticos.De lo contrario, acostúmbrate a duplicar ese código.Lo siento.

Cree una clase de utilidad con seguridad de tipos que cargue enumeraciones por código:

La interfaz se reduce a:

public interface CodeableEnum {
    String getCode();
}

La clase de utilidad es:

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 de prueba que demuestra el uso:

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

Ahora solo está duplicando el método getCode() y el método getByCode() está en un solo lugar.También sería bueno incluir todas las excepciones en una única RuntimeException :)

Aquí tengo otra solución:

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

El truco es que la clase interna se puede utilizar para contener "métodos globales".

Funcionó bastante bien para mí.Ok, debe implementar 3 métodos, pero esos métodos son solo delegadores.

Parece que en realidad está implementando información de tipo de tiempo de ejecución.Java proporciona esto como una característica del lenguaje.

Te sugiero que busques RTTI o reflexión.

No creo que esto sea posible.Sin embargo, podría usar el método valueOf(String name) de la enumeración si fuera a usar el nombre del valor de la enumeración como código.

¿Qué tal un método genérico estático?Puede reutilizarlo desde los métodos getByCode() de su enumeración o simplemente usarlo directamente.Siempre uso identificadores enteros para mis enumeraciones, por lo que mi método getById() solo tiene que hacer esto:valores de retorno()[id].Es mucho más rápido y sencillo.

Si realmente quieres heredar, no olvides que puedes implementar el patrón de enumeración usted mismo, como en los viejos días de Java 1.4.

Lo más cerca que estuve de lo que deseaba fue crear una plantilla en IntelliJ que 'implementaría' el código genérico (usando el valor de enumeración (nombre de cadena)).No es perfecto pero funciona bastante bien.

En su caso específico, los métodos getCode()/getByCode(String code) parecen muy cerrados (eufemísticamente hablando) al comportamiento de los métodos toString()/valueOf(String value) proporcionados por todas las enumeraciones.¿Por qué no quieres usarlos?

Otra solución sería no poner nada en la enumeración en sí y simplemente proporcionar un código Enum <-> de mapa bidireccional para cada enumeración.Podrías, p.usar InmutableBiMap de Colecciones de Google para esto.

De esa manera no habrá ningún código duplicado.

Ejemplo:

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

En mi opinión, esta sería la forma más fácil, sin reflexionar y sin agregar ningún contenedor adicional a su enumeración.

Creas una interfaz que tu enumeración implementa:

public interface EnumWithId {

    public int getId();

}

Luego, en una clase auxiliar, simplemente crea un método como este:

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

Este método podría usarse así:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top