java - values - eliminando el código duplicado de Enum
java enum valueof (15)
¿Qué tal un método genérico estático? Puedes reutilizarlo desde los métodos getByCode () de tu enum o simplemente usarlo directamente. Siempre utilizo los ID enteros para mis enumeraciones, por lo que mi método getById () solo tiene que hacer esto: return values () [id]. Es mucho más rápido y simple.
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 se 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:
- Una enumeración no puede extender una clase
- Los elementos de una enumeración (SKYPE, GOOGLE_TALK, etc.) no pueden extender una clase
- No puedo proporcionar una implementación predeterminada de getByCode (), porque DefaultCodableEnum no es en sí mismo un Enum. Traté de cambiar DefaultCodableEnum para extender java.lang.Enum, pero esto no parece estar permitido.
¿Alguna sugerencia que no se base en la reflexión? Gracias, Don
Aproximadamente lo más cercano que pude a lo que deseaba era crear una plantilla en IntelliJ que ''implementara'' el código genérico (usando el valor de enum de (nombre de la cadena)). No es perfecto, pero funciona bastante bien.
Desafortunadamente, no creo que haya una manera de hacer esto. Lo mejor que puede hacer es renunciar por completo a emums y usar extensión convencional y miembros estáticos. De lo contrario, acostúmbrate a duplicar ese código. Lo siento.
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 lo agregue:
http://freddy33.blogspot.com/2007/11/abstract-enum-ricky-carlson-way.html
Sun RFE:
No creo que esto sea posible. Sin embargo, podría usar el método value_flug (nombre de la cadena) de la enumeración si fuera a usar el nombre del valor enum como su código.
Parece que en realidad está implementando información de tipo de tiempo de ejecución. Java proporciona esto como una función de idioma.
Te sugiero que busques RTTI o reflexión.
Si realmente quieres la herencia, no olvides que puedes implementar el patrón de enumeración tú mismo , como en el mal antiguo Java de 1.4 días.
Cree una clase de utilidad de tipo seguro que cargará 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 :)
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?
Tuve un problema similar con un componente de localización que escribí. Mi componente está diseñado para acceder a mensajes localizados con constantes enum que indexan en un paquete de recursos, no es un problema difícil.
Descubrí que estaba copiando y pegando el mismo código enum de "plantilla" en todo el lugar. Mi solución para evitar la duplicación es un generador de código que acepta un archivo de configuración XML con los nombres constantes enum y los argumentos 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 todos los códigos duplicados. En todas partes tendría código fuente enum, ahora hay un archivo de configuración XML. Mis scripts de compilación detectan los archivos generados desactualizados e invocan al generador de códigos para crear el código enum.
Puedes ver este componente aquí . La plantilla que estaba copiando y pegando se incluye en una hoja de estilos XSLT . El generador de código ejecuta la transformación de la hoja de estilo. Un archivo de entrada es bastante conciso en comparación con el código fuente enum generado.
HTH,
Greg
Para poner en orden 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));
}
}
Otra solución sería no poner nada en la enumeración en sí, y simplemente proporcionar un mapa bidireccional Enum <-> Código para cada enumeración. Por ejemplo, podría usar ImmutableBiMap de Google Collections para esto.
De esa forma no hay código duplicado en absoluto.
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 manera más fácil, sin reflexión y sin agregar ningún contenedor adicional a su enum.
Usted crea una interfaz que implementa su enumeración:
public interface EnumWithId {
public int getId();
}
Luego, en una clase de ayuda, simplemente creas 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 ser usado así:
MyUtil.getInstance().getById(MyEnum.class, myEnumId);
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 usar para contener "métodos globales".
Funcionó bastante bien para mí. OK, tienes que implementar 3 métodos, pero esos métodos son solo delegadores.
Puede factorizar el código duplicado en una clase CodeableEnumHelper
:
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 clase CodeableEnum
todavía tendría que implementar un método getByCode
, 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());
}
}