Sostituisci valueof () e toString () nell'enumerazione Java


122

I valori nel mio enumsono parole che devono contenere spazi, ma le enumerazioni non possono avere spazi nei loro valori, quindi è tutto raggruppato. Voglio sovrascrivere toString()per aggiungere questi spazi dove lo dico.

Voglio anche che l'enumerazione fornisca l'enumerazione corretta quando uso valueOf()sulla stessa stringa a cui ho aggiunto gli spazi.

Per esempio:

public enum RandomEnum
{
     StartHere,
     StopHere
}

La chiamata toString()sul RandomEnumcui valore è StartHererestituisce una stringa "Start Here". La chiamata valueof()sulla stessa stringa ( "Start Here") restituisce il valore enum StartHere.

Come posso fare questo?


2
Aggiungi il tag "Java" per ottenere più commenti / risposte.
Java42

Risposte:


197

Puoi provare questo codice. Poiché non è possibile sovrascrivere il valueOfmetodo, è necessario definire un metodo personalizzato ( getEnumnel codice di esempio riportato di seguito) che restituisca il valore necessario e modificare il client per utilizzare invece questo metodo.

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

4
getEnum potrebbe essere abbreviato se si esegue quanto segue: v.getValue (). equals (value); il controllo nullo può essere omesso.
rtcarlson

Puoi anche costruire pigramente una mappa e riutilizzarla, ma fai attenzione ai problemi di threading durante la creazione.
Maarten Bodewes

11
non funziona in scenari in cui non è possibile modificare la chiamata del metodo valueOf () in getValue (), ad esempio quando si definiscono determinati campi tramite le annotazioni Hibernate per mappare i valori enum
mmierins

Fino a quando gli enum non vengono risolti in Java, @Bat ha una soluzione più appropriata e amichevole OO. name () non dovrebbe essere definitivo.
Andrew T Finnell

invece di per puoi usare valueOf (RandomEnum.class, value);
Wojtek

22

Prova questo, ma non sono sicuro che funzionerà ovunque :)

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

18
Perché sono l'unico a cui quel genere di cose sembra malvagia? Voglio dire, sembra davvero interessante, ma qualcuno senza contesto che legge quel codice molto probabilmente sarebbe come "WTF?".
Nicolas Guillaume

11
@NicolasGuillaume Questo è il tipo di codice che, se implementato, avrebbe un disperato bisogno di un commento che spieghi perché è presente e quale problema il programmatore stava cercando di risolvere. :-)
Ti Strga

9
Non prendere l'eccezione generica, in quanto potrebbe essere OutOfMemory o altro
crusy

2
Questa è un'idea terribile. Non ultima la possibilità di errore silenzioso senza indicazione o ripristino. Anche ora il valore "nome" e il valore simbolico nel codice (es. A, B) sono diversi. +2 per essere intelligente. -200 per essere terribilmente intelligente. Non avrei mai approvato questa revisione del codice.
pedorro

1
@pedorro Questo non sembra essere così terribile come lo stai immaginando. Chiunque abbia capito che tutto ciò che fa il comitato Java non dovrebbe essere considerato come Gospel lo approverebbe in una revisione del codice. È infinitamente peggio dover riscrivere e poi mantenere tutti i metodi di utilità Enum perché name () non può essere sovrascritto. Invece di catturare l'eccezione e inghiottirla, dovrebbe essere lanciata una RuntimeException e quindi va bene.
Andrew T Finnell

14

È possibile utilizzare una mappa statica nella propria enumerazione che associa le stringhe alle costanti enumerate. Usalo in un metodo statico 'getEnum'. Ciò evita la necessità di iterare le enumerazioni ogni volta che si desidera ottenerne una dal valore 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;
    }
}

Assicurati solo che l'inizializzazione statica della mappa avvenga sotto la dichiarazione delle costanti enum.

BTW - quel tipo di "ImmutableMap" proviene dall'API guava di Google e lo consiglio vivamente in casi come questo.


EDIT - Per i commenti:

  1. Questa soluzione presuppone che ogni valore di stringa assegnato sia univoco e non nullo. Dato che il creatore dell'enumerazione può controllarlo e che la stringa corrisponde al valore enum univoco e non nullo, questa sembra una restrizione sicura.
  2. Ho aggiunto il metodo "toSTring ()" come richiesto nella domanda

1
Ho ricevuto un paio di voti negativi su questa risposta: a qualcuno interessa espandere il motivo per cui questa risposta è cattiva? Sono solo curioso di sapere cosa pensano le persone.
pedorro

Ciò non andrà a buon fine per lo scenario con più costanti enum con lo stesso "valore" RestartHere("replay"), ReplayHere("replay")o nessun "valore" simile PauseHere, WaitHere(ovvero enum ha un costruttore predefinito qualcosa di simileprivate RandomEnum() { this.strVal = null; }
sactiw

1
Dovresti usare ImmutableMap.Builder invece di creare una MutableMap per costruire + ImmutableMap :: copyOf.
Bryan Correll

Sembra interessante, ma se l'enum ha solo pochi valori diversi, non ci vorrà molto per iterarli. Probabilmente ne vale la pena solo se l'enumerazione ha molti valori.
Ben Thurley

13

Che ne dici di un'implementazione di Java 8? (null può essere sostituito dal tuo Enum predefinito)

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

Oppure potresti usare:

...findAny().orElseThrow(NotFoundException::new);

2

Non credo che riuscirai a far funzionare valueOf ("Inizia qui"). Ma per quanto riguarda gli spazi ... prova quanto segue ...

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

2

Quella che segue è una bella alternativa generica a 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);
}

l'uso di è compareTo()più elegante che equals()per le corde?
Betlista

L'eleganza non è un problema: sono d'accordo che .equals()funzionerebbe bene. Potrebbe usare ==se ti piace. (Anche se una buona ragione per non farlo è se dovessi mai sostituire l'enumerazione con una classe.)
eccezione il

3
@exception NON USARE MAI == per i confronti di stringhe poiché eseguirà il controllo dell'uguaglianza degli oggetti mentre ciò di cui hai bisogno è il controllo dell'uguaglianza del contenuto e per questo usa il metodo equals () della classe String.
sactiw

2

Hai ancora un'opzione per implementare nella tua enum questo:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.