Переопределить valueof () и toString () в перечислении Java


122

Значения в моем enum- это слова, в которых должны быть пробелы, но перечисления не могут содержать пробелов в своих значениях, поэтому все они сгруппированы. Я хочу переопределить toString()добавление этих пробелов там, где я это скажу.

Я также хочу, чтобы перечисление предоставляло правильное перечисление, когда я использую valueOf()ту же строку, в которую я добавил пробелы.

Например:

public enum RandomEnum
{
     StartHere,
     StopHere
}

Вызов toString()на RandomEnumзначение которого StartHereвозвращается строка "Start Here". Вызов valueof()той же строки ( "Start Here") возвращает значение перечисления StartHere.

Как я могу это сделать?


2
Добавьте тег «Java», чтобы получать больше комментариев / ответов.
Java42

Ответы:


197

Вы можете попробовать этот код. Поскольку вы не можете переопределить valueOfметод, вам необходимо определить собственный метод ( getEnumв приведенном ниже примере кода), который возвращает нужное вам значение, и изменить своего клиента на использование этого метода.

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 можно сократить, если вы сделаете следующее: v.getValue (). equals (value); нулевую проверку можно опустить.
rtcarlson

Вы также можете лениво построить карту и повторно использовать ее, но остерегайтесь проблем с потоками при ее создании.
Maarten Bodewes

11
не работает в сценариях, где вы не можете изменить вызов метода valueOf () на getValue (), например, когда вы определяете определенные поля с помощью аннотаций Hibernate для сопоставления со значениями перечисления
mmierins

Пока Enums не будет исправлено в Java, у @Bat есть более подходящее и дружественное к объектно-ориентированному программированию решение. name () не должно быть окончательным.
Эндрю Т. Финнелл

вместо for вы можете использовать valueOf (RandomEnum.class, value);
Войтек

22

Попробуйте, но я не уверен, что это сработает везде :)

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
Почему я единственный, кому подобные вещи кажутся злыми? Я имею в виду, что это выглядит действительно круто, но тот, кто читает этот код без контекста, скорее всего, скажет «WTF?».
Николя Гийом

11
@NicolasGuillaume Это тот код, который, если бы он был реализован, отчаянно нуждался бы в комментариях, объясняющих, почему он существует и какую проблему пытается решить программист. :-)
Ti Strga 06

9
Не перехватывайте общее исключение, так как это может быть OutOfMemory или что-то
crusy

2
Это ужасная идея. Не последним из них является возможность тихой ошибки без индикации или восстановления. Также теперь значение «имени» и символическое значение в коде (например, A, B) отличаются. +2 за ум. -200 за то, что был ужасно умен. Я бы ни за что не одобрил этот обзор кода.
pedorro

1
@pedorro Кажется, это не так ужасно, как вы думаете. Любой, кто понимает, что все, что делает комитет Java, не должно восприниматься как Евангелие, прошел бы это в Code Review. Бесконечно хуже переписывать, а затем поддерживать все служебные методы Enum, потому что name () нельзя переопределить. Вместо того, чтобы перехватить исключение и проглотить его, должно быть создано исключение RuntimeException, и тогда это нормально.
Эндрю Т. Финнелл

14

Вы можете использовать статическую карту в своем перечислении, которая сопоставляет строки с константами перечисления. Используйте его в статическом методе getEnum. Это избавляет от необходимости перебирать перечисления каждый раз, когда вы хотите получить одно из его значения 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;
    }
}

Просто убедитесь, что статическая инициализация карты происходит под объявлением констант перечисления.

Кстати, этот тип ImmutableMap взят из Google guava API, и я определенно рекомендую его в подобных случаях.


РЕДАКТИРОВАТЬ - Согласно комментариям:

  1. Это решение предполагает, что каждое присвоенное строковое значение уникально и не равно нулю. Учитывая, что создатель перечисления может управлять этим, и что строка соответствует уникальному и ненулевому значению перечисления, это кажется безопасным ограничением.
  2. Я добавил метод toSTring (), как просили в вопросе

1
Я получил пару голосов против этого ответа - кто-нибудь хочет рассказать, почему этот ответ плохой? Мне просто любопытно, что думают люди.
pedorro

Это не сработает для сценария, в котором есть несколько констант перечисления с одним и тем же «значением», например, RestartHere("replay"), ReplayHere("replay")или вообще без «значения» PauseHere, WaitHere(т.е. enum имеет конструктор по умолчанию, например,private RandomEnum() { this.strVal = null; }
sactiw

1
Вы должны использовать ImmutableMap.Builder вместо создания MutableMap для сборки + ImmutableMap :: copyOf.
Брайан Коррелл

Это выглядит интересно, но если перечисление имеет только несколько разных значений, то перебирать их не займет много времени. Вероятно, это того стоит, только если перечисление имеет много значений.
Бен Терли

13

Как насчет реализации Java 8? (null можно заменить вашим Enum по умолчанию)

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

Или вы можете использовать:

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

2

Я не думаю, что вам удастся заставить valueOf ("Начать здесь") работать. Но что касается пробелов ... попробуйте следующее ...

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

Ниже приводится хорошая общая альтернатива 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);
}

использование compareTo()более элегантного, чем equals()для строк?
Betlista

Элегантность не проблема - я согласен, что .equals()все подойдет. Можете использовать, ==если хотите. (Хотя есть веская причина не делать этого, если вы когда-нибудь заменяете перечисление классом.)
исключение

3
@exception НИКОГДА НЕ ИСПОЛЬЗУЙТЕ == для сравнения строк, поскольку он будет выполнять проверку равенства объектов, в то время как вам нужна проверка равенства содержимого и для этого используйте метод equals () класса String.
жертву

2

У вас все еще есть возможность реализовать в своем перечислении следующее:

public static <T extends Enum<T>> T valueOf(Class<T> enumType, String name){...}
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.