Zastąp valueof () i toString () w wyliczeniu Java


122

Wartości w moim enumto słowa, które muszą zawierać spacje, ale wyliczenia nie mogą mieć spacji w swoich wartościach, więc wszystko jest zebrane. Chcę nadpisać, toString()aby dodać te spacje tam, gdzie to powiem.

Chcę również, aby wyliczenie zapewniało prawidłowe wyliczenie, gdy używam valueOf()tego samego ciągu, do którego dodałem spacje.

Na przykład:

public enum RandomEnum
{
     StartHere,
     StopHere
}

Zaproszenie toString()na RandomEnumktórego wartość StartHerepowraca ciąg "Start Here". Wywołanie valueof()tego samego ciągu ( "Start Here") zwraca wartość wyliczenia StartHere.

W jaki sposób mogę to zrobić?


2
Dodaj znacznik „Java”, aby uzyskać więcej komentarzy / odpowiedzi.
Java 42,

Odpowiedzi:


197

Możesz wypróbować ten kod. Ponieważ nie możesz przesłonić valueOfmetody, musisz zdefiniować niestandardową metodę ( getEnumw przykładowym kodzie poniżej), która zwraca potrzebną wartość i zmienić klienta, aby używał tej metody.

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 można skrócić, wykonując następujące czynności: v.getValue (). equals (value); sprawdzenie zerowe można pominąć.
rtcarlson

Możesz także leniwie zbudować mapę i użyć jej ponownie, ale uważaj na problemy z wątkami podczas jej tworzenia.
Maarten Bodewes

11
nie działa w scenariuszach, w których nie można zmienić wywołania metody valueOf () na getValue () np. podczas definiowania pewnych pól za pomocą adnotacji Hibernate w celu odwzorowania na wartości wyliczenia
mmierins

Dopóki Enum nie zostanie naprawione w Javie, @Bat ma bardziej odpowiednie i przyjazne dla OO rozwiązanie. nazwa () nie powinna być ostateczna.
Andrew T Finnell

zamiast for możesz użyć valueOf (RandomEnum.class, value);
Wojtek

22

Spróbuj tego, ale nie jestem pewien, czy zadziała wszędzie :)

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
Dlaczego jestem jedyną osobą, której takie rzeczy wyglądają źle? Chodzi mi o to, że wygląda naprawdę fajnie, ale ktoś bez kontekstu, który czyta ten kod, najprawdopodobniej powiedziałby „WTF?”.
Nicolas Guillaume,

11
@NicolasGuillaume To rodzaj kodu, który, gdyby został zaimplementowany, rozpaczliwie potrzebowałby komentarza wyjaśniającego, dlaczego tam jest i jaki problem programista próbował rozwiązać. :-)
Ti Strga

9
Nie
łapaj

2
To okropny pomysł. Nie najmniej ważnym z nich jest możliwość cichego błędu bez wskazania lub przywrócenia. Również teraz wartość „nazwa” i wartość symboliczna w kodzie (np. A, B) są różne. +2 za spryt. -200 za bycie strasznie sprytnym. Nie ma mowy, żebym kiedykolwiek zatwierdził to przegląd kodu.
pedorro

1
@pedorro To nie wydaje się być tak straszne, jak się wydaje. Każdy, kto zrozumiał, że wszystko, co robi komitet Java, nie powinno być traktowane jako Ewangelia, zdałby to w przeglądzie kodu. Nieskończenie gorsze jest ponowne napisanie, a następnie utrzymanie wszystkich metod narzędzia Enum, ponieważ nie można nadpisać name (). Zamiast przechwytywać wyjątek i połykać go, należy zgłosić wyjątek RuntimeException i to jest w porządku.
Andrew T Finnell

14

Możesz użyć statycznej mapy w swoim wyliczeniu, która mapuje ciągi na stałe wyliczeniowe. Użyj go w metodzie statycznej „getEnum”. Pomija to potrzebę iteracji przez wyliczenia za każdym razem, gdy chcesz uzyskać jedną z jej wartości 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;
    }
}

Po prostu upewnij się, że statyczna inicjalizacja mapy następuje poniżej deklaracji stałych wyliczeniowych.

Przy okazji - ten typ „ImmutableMap” pochodzi z Google guava API i zdecydowanie polecam go w takich przypadkach.


EDYCJA - Zgodnie z komentarzami:

  1. To rozwiązanie zakłada, że ​​każda przypisana wartość ciągu jest unikalna i różna od null. Biorąc pod uwagę, że twórca wyliczenia może to kontrolować i że ciąg odpowiada unikatowej i niezerowej wartości wyliczenia, wydaje się to bezpiecznym ograniczeniem.
  2. Dodałem metodę „toSTring ()” zgodnie z pytaniem

1
Otrzymałem kilka głosów negatywnych na tę odpowiedź - czy ktoś chciałby wyjaśnić, dlaczego ta odpowiedź jest zła? Jestem po prostu ciekawy, co myślą ludzie.
pedorro

To się nie powiedzie dla scenariusza z wieloma stałymi wyliczenia z taką samą „wartością” podobną RestartHere("replay"), ReplayHere("replay")lub bez „wartości” podobną PauseHere, WaitHere(tj. Enum ma domyślny konstruktor podobny doprivate RandomEnum() { this.strVal = null; }
sactiw

1
Powinieneś użyć ImmutableMap.Builder zamiast tworzyć MutableMap do budowania + ImmutableMap :: copyOf.
Bryan Correll,

Wygląda to interesująco, ale jeśli wyliczenie ma tylko kilka różnych wartości, iteracja po nich nie zajmie dużo czasu. Prawdopodobnie jest tego warte tylko wtedy, gdy wyliczenie ma wiele wartości.
Ben Thurley

13

A co z implementacją Java 8? (wartość null można zastąpić domyślnym wyliczeniem)

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

Lub możesz użyć:

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

2

Nie sądzę, żebyś osiągnął valueOf („Zacznij tutaj”) do pracy. Ale jeśli chodzi o spacje ... spróbuj następujących rzeczy ...

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

Poniżej znajduje się ładna ogólna alternatywa dla 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);
}

jest compareTo()bardziej eleganckie niż w equals()przypadku Strings?
Betlista

Elegancja nie jest problemem - zgadzam się, że .equals()będzie dobrze. Możesz użyć, ==jeśli chcesz. (Chociaż dobrym powodem, aby tego nie robić, jest zastąpienie wyliczenia klasą).
wyjątek

3
@exception NIGDY NIE UŻYWAJ == do porównań ciągów, ponieważ będzie sprawdzać równość obiektów, podczas gdy potrzebujesz sprawdzenia równości zawartości i do tego użyj metody equals () klasy String.
sactiw

2

Nadal masz możliwość zaimplementowania w swoim wyliczeniu tego:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.