Substituir valueof () e toString () na enumeração Java

122

Os valores em meu enumsão palavras que precisam ter espaços neles, mas enumerações não podem ter espaços em seus valores; portanto, tudo está agrupado. Eu quero substituir toString()para adicionar esses espaços onde eu digo.

Também quero que a enumeração forneça a enumeração correta quando uso valueOf()a mesma sequência em que adicionei os espaços.

Por exemplo:

public enum RandomEnum
{
     StartHere,
     StopHere
}

Chamada toString()em RandomEnumcujo valor é StartHereseqüência de retornos "Start Here". A chamada valueof()nessa mesma string ( "Start Here") retorna um valor de enumeração StartHere.

Como posso fazer isso?

WildBamaBoy
fonte
2
Adicione a tag "Java" para obter mais comentários / respostas.
Java42
possível duplicata de Implementação toString sobre enums Java
Steve Chambers

Respostas:

197

Você pode experimentar este código. Como você não pode substituir o valueOfmétodo, é necessário definir um método personalizado ( getEnumno código de exemplo abaixo) que retorne o valor necessário e altere seu cliente para usar esse método.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private String value;

    RandomEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }

    @Override
    public String toString() {
        return this.getValue();
    }

    public static RandomEnum getEnum(String value) {
        for(RandomEnum v : values())
            if(v.getValue().equalsIgnoreCase(value)) return v;
        throw new IllegalArgumentException();
    }
}
Jugal Shah
fonte
4
getEnum pode ser reduzido se você fizer o seguinte: v.getValue (). equals (value); a verificação nula pode ser omitida.
Rtcarlson 27/03
Você também pode construir preguiçosamente um mapa e reutilizá-lo, mas cuidado com os problemas de segmentação enquanto o constrói.
Maarten Bodewes
11
não trabalho dentro de cenários onde você não pode mudar a vocação de valueOf () para getValue () por exemplo, quando você definir determinados campos através de anotações do Hibernate para mapear valores enum
mmierins
Até que as enums sejam corrigidas em Java, o @Bat tem uma solução mais apropriada e amigável para OO. name () não deve ser final.
Andrew T Finnell
em vez de para você pode usar valueOf (RandomEnum.class, value);
Wojtek
22

Tente isso, mas não sei se isso funcionará em todos os lugares :)

public enum MyEnum {
    A("Start There"),
    B("Start Here");

    MyEnum(String name) {
        try {
            Field fieldName = getClass().getSuperclass().getDeclaredField("name");
            fieldName.setAccessible(true);
            fieldName.set(this, name);
            fieldName.setAccessible(false);
        } catch (Exception e) {}
    }
}
Bastão
fonte
18
Por que eu sou o único a quem esse tipo de coisa parece ruim? Quero dizer, parece muito legal, mas alguém sem contexto que lê esse código provavelmente seria como "WTF?".
Nicolas Guillaume
11
@ NicolasGuillaume Esse é o tipo de código que, se implementado, precisaria desesperadamente de um comentário explicando por que está lá e que problema o programador estava tentando resolver. :-)
Ti Strga 06/06
9
Não pegar exceção genérica, como isso pode ser OutOfMemory ou qualquer coisa
crusy
2
Esta é uma péssima ideia. Não menos importante é a possibilidade de erro silencioso sem indicação ou recuperação. Agora também o valor "nome" e o valor simbólico no código (por exemplo, A, B) são diferentes. +2 por ser inteligente. -200 por ser terrivelmente inteligente. Não há como eu aprovar isso como uma revisão de código.
pedorro
1
@pedorro Isso não parece tão terrível quanto você está imaginando. Qualquer um que entenda que tudo o que o comitê Java faz não deve ser considerado como Evangelho passaria isso em uma Revisão de Código. É infinitamente pior ter que reescrever e manter todos os métodos do utilitário Enum porque o nome () não pode ser substituído. Em vez de capturar a exceção e engoli-la, uma RuntimeException deve ser lançada e isso é bom.
Andrew T Finnell
14

Você pode usar um mapa estático em sua enumeração que mapeia Strings para constantes de enumeração. Use-o em um método estático 'getEnum'. Isso ignora a necessidade de iterar pelas enumerações sempre que você deseja obter uma do valor String.

public enum RandomEnum {

    StartHere("Start Here"),
    StopHere("Stop Here");

    private final String strVal;
    private RandomEnum(String strVal) {
        this.strVal = strVal;
    }

    public static RandomEnum getEnum(String strVal) {
        if(!strValMap.containsKey(strVal)) {
            throw new IllegalArgumentException("Unknown String Value: " + strVal);
        }
        return strValMap.get(strVal);
    }

    private static final Map<String, RandomEnum> strValMap;
    static {
        final Map<String, RandomEnum> tmpMap = Maps.newHashMap();
        for(final RandomEnum en : RandomEnum.values()) {
            tmpMap.put(en.strVal, en);
        }
        strValMap = ImmutableMap.copyOf(tmpMap);
    }

    @Override
    public String toString() {
        return strVal;
    }
}

Apenas verifique se a inicialização estática do mapa ocorre abaixo da declaração das constantes enum.

BTW - esse tipo 'ImmutableMap' é da API do Google goiaba, e eu definitivamente recomendo em casos como este.


EDIT - Pelos comentários:

  1. Esta solução pressupõe que cada valor de sequência atribuído seja exclusivo e não nulo. Dado que o criador do enum pode controlar isso, e que a string corresponde ao valor de enum exclusivo e não nulo, isso parece uma restrição segura.
  2. Adicionei o método 'toSTring ()' conforme solicitado na pergunta
pedorro
fonte
1
Recebi alguns votos negativos nesta resposta - alguém se preocupa em explicar por que essa resposta é ruim? Só estou curioso no que as pessoas estão pensando.
Pedorro
Isso falhará no cenário com várias constantes enum com o mesmo 'valor' igual RestartHere("replay"), ReplayHere("replay")ou nenhum 'valor' igual PauseHere, WaitHere(ou seja, o enum tem um construtor padrão semelhante aprivate RandomEnum() { this.strVal = null; }
sactiw
1
Você deve usar ImmutableMap.Builder em vez de criar um MutableMap para criar + ImmutableMap :: copyOf.
Bryan Correll
Isso parece interessante, mas se o enum tiver apenas alguns valores diferentes, não demorará muito para iterar sobre eles. Provavelmente só vale a pena se o enum tiver muitos valores.
Ben Thurley
13

Que tal uma implementação do Java 8? (null pode ser substituído pelo seu Enum padrão)

public static RandomEnum getEnum(String value) {
    List<RandomEnum> list = Arrays.asList(RandomEnum.values());
    return list.stream().filter(m -> m.value.equals(value)).findAny().orElse(null);
}

Ou você pode usar:

...findAny().orElseThrow(NotFoundException::new);
corlaez
fonte
2

Eu não acho que você vai obter valor de ("Start Here") para funcionar. Mas quanto aos espaços ... tente o seguinte ...

static private enum RandomEnum {
    R("Start There"), 
    G("Start Here"); 
    String value;
    RandomEnum(String s) {
        value = s;
    }
}

System.out.println(RandomEnum.G.value);
System.out.println(RandomEnum.valueOf("G").value);

Start Here
Start Here
Java42
fonte
2

A seguir, é uma boa alternativa genérica para valueOf ()

public static RandomEnum getEnum(String value) {
  for (RandomEnum re : RandomEnum.values()) {
    if (re.description.compareTo(value) == 0) {
      return re;
    }
  }
  throw new IllegalArgumentException("Invalid RandomEnum value: " + value);
}
exceção
fonte
o uso é compareTo()mais elegante do que equals()para Strings?
Betlista 27/01
Elegância não é um problema - concordo que .equals()funcionaria bem. Poderia usar ==se você gosta. (Embora uma boa razão para não fazer isso seja, você deve substituir o enum por uma classe.)
exceção
3
@exception NUNCA USE == para comparações de String, pois ele fará a verificação de igualdade de objetos enquanto você precisa da verificação de igualdade de conteúdo e, para isso, use o método equals () da classe String.
sactiw
2

Você ainda tem uma opção para implementar em sua enumeração isso:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Andrey Lebedenko
fonte