Pergunta

Tenho um grande número de Enums que implementam esta interface:

/**
 * 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();
}

Um exemplo típico é:

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 você pode imaginar, esses métodos são virtualmente idênticos em todas as implementações do CodableEnum.Gostaria de eliminar esta duplicação, mas francamente não sei como.Tentei usar uma classe como a seguinte:

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

Mas isso acaba sendo bastante inútil porque:

  1. Um enum não pode estender uma classe
  2. Elementos de um enum (SKYPE, GOOGLE_TALK, etc.) não podem estender uma classe
  3. Não posso fornecer uma implementação padrão de getByCode(), porque DefaultCodableEnum não é um Enum.Tentei alterar DefaultCodableEnum para estender java.lang.Enum, mas isso não parece ser permitido.

Alguma sugestão que não dependa de reflexão?Obrigado, Don

Foi útil?

Solução

Você poderia fatorar o código duplicado em um CodeableEnumHelper aula:

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 classe ainda teria que implementar um getByCode método, mas a implementação real do método foi pelo menos centralizada em um único lugar.

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

Outras dicas

Enums abstratos são potencialmente muito úteis (e atualmente não são permitidos).Mas existe uma proposta e um protótipo se você quiser pressionar alguém na Sun para adicioná-lo:

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

RFE solar:

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

Para organizar o 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());
    } 
}

Ou de forma mais eficiente:

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

Tive um problema semelhante com um componente de localização que escrevi.Meu componente foi projetado para acessar mensagens localizadas com constantes enum que indexam em um pacote de recursos, o que não é um problema difícil.

Descobri que estava copiando e colando o mesmo código enum de "modelo" em todos os lugares.Minha solução para evitar a duplicação é um gerador de código que aceita um arquivo de configuração XML com os nomes das constantes enum e argumentos do construtor.A saída é o código-fonte Java com os comportamentos "duplicados".

Agora mantenho os arquivos de configuração e o gerador, não todo o código duplicado.Em todos os lugares onde eu teria código-fonte enum, agora existe um arquivo de configuração XML.Meus scripts de construção detectam arquivos gerados desatualizados e invocam o gerador de código para criar o código enum.

Você pode ver este componente aqui.O modelo que eu estava copiando e colando é fatorado em uma folha de estilo XSLT.O gerador de código executa a transformação da folha de estilo.Um Arquivo de entrada é bastante conciso em comparação com o código-fonte enum gerado.

HTH,
Greg

Infelizmente, não acho que haja uma maneira de fazer isso.Sua melhor aposta provavelmente seria desistir completamente dos emums e usar extensão de classe convencional e membros estáticos.Caso contrário, acostume-se a duplicar esse código.Desculpe.

Crie uma classe de utilitário com segurança de tipo que carregará enums por código:

A interface se resume a:

public interface CodeableEnum {
    String getCode();
}

A classe de utilidade é:

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

}

Um caso de teste demonstrando o 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;
    }   
}
}

Agora você está apenas duplicando o método getCode() e o método getByCode() está em um só lugar.Pode ser bom agrupar todas as exceções em uma única RuntimeException também :)

Aqui tenho outra solução:

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

O truque é que a classe interna pode ser usada para armazenar "métodos globais".

Funcionou muito bem para mim.OK, você precisa implementar 3 métodos, mas esses métodos são apenas delegadores.

Parece que você está realmente implementando informações de tipo de tempo de execução.Java fornece isso como um recurso de linguagem.

Eu sugiro que você procure RTTI ou reflexão.

Eu não acho que isso seja possível.No entanto, você poderia usar o método valueOf(String name) da enum se fosse usar o nome do valor da enumeração como seu código.

Que tal um método genérico estático?Você pode reutilizá-lo nos métodos getByCode() do seu enum ou simplesmente usá-lo diretamente.Eu sempre uso IDs inteiros para minhas enumerações, então meu método getById() só precisa fazer isso:valores de retorno()[id].É muito mais rápido e simples.

Se você realmente quer herança, não esqueça que você pode implemente você mesmo o padrão enum, como no velho Java 1.4 dias.

O mais próximo que cheguei do que você queria era criar um modelo no IntelliJ que 'implementasse' o código genérico (usando valueOf(String name) do enum).Não é perfeito, mas funciona muito bem.

No seu caso específico, os métodos getCode() / getByCode(String code) parecem muito fechados (eufemisticamente falando) ao comportamento dos métodos toString() / valueOf(String value) fornecidos por todas as enumerações.Por que você não quer usá-los?

Outra solução seria não colocar nada no próprio enum e apenas fornecer um mapa bidirecional Enum <-> Code para cada enum.Você poderia, por exemplo.usar BiMap Imutável das Coleções do Google para isso.

Dessa forma, não há nenhum código duplicado.

Exemplo:

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

Na minha opinião, esta seria a maneira mais fácil, sem reflexão e sem adicionar nenhum wrapper extra ao seu enum.

Você cria uma interface que seu enum implementa:

public interface EnumWithId {

    public int getId();

}

Então, em uma classe auxiliar, você apenas cria um 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 poderia ser usado assim:

MyUtil.getInstance().getById(MyEnum.class, myEnumId);
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top