Подтвердить что ты не робот

Каково использование значения по умолчанию, когда переключатель предназначен для перечисления?

Предположим, что у меня есть перечисление Color с двумя возможными значениями: RED и BLUE:

public enum Color {
    RED,
    BLUE
}

Теперь предположим, что у меня есть оператор switch для этого перечисления, где у меня есть код для обоих возможных значений:

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
   ...
   break;

case BLUE:
   ...
   break;

default:
   break;
}

Поскольку у меня есть блок кода для обоих возможных значений перечисления, каково использование default в приведенном выше коде?

Должен ли я делать исключение, если код каким-то образом достигает блока default, как это?

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
case RED:
   ...
   break;

case BLUE:
   ...
   break;

default:
   throw new IllegalArgumentException("This should not have happened");
}
4b9b3361

Ответ 1

Хорошей практикой является бросить исключение, как вы показали во втором примере. Вы улучшаете ремонтопригодность своего кода, быстро отказываясь.

В этом случае это означало бы, если вы позже (возможно, годы спустя) добавите значение enum и достигнете инструкции switch, вы сразу обнаружите ошибку.

Если значение по умолчанию не было установлено, возможно, код будет работать даже с новым значением перечисления и может иметь нежелательное поведение.

Ответ 2

Другие ответы правильны, говоря, что вы должны внедрить ветвь default, которая выдает исключение, в случае, если новое значение будет добавлено к вашему перечислению в будущем. Однако я хотел бы сделать еще один шаг и спросить, почему вы даже используете оператор switch.

В отличие от языков, таких как С++ и С#, Java представляет значения Enum как реальные объекты, что означает, что вы можете использовать объектно-ориентированное программирование. Скажем, что целью вашего метода является предоставление значения RGB для каждого цвета:

switch (color)
    case RED:
       return "#ff0000";
    ...

Ну, возможно, если вы хотите, чтобы каждый цвет имел значение RGB, вы должны включить это как часть своего описания:

public enum Color
{
    RED("#FF0000"),
    BLUE("#0000FF");

    String rgb;
    public Color(String rgb) {
        this.rgb = rgb;
    }
    public getRgb() { return this.rgb; }
}

Таким образом, если вы добавите новый цвет позже, вы в значительной степени вынуждены предоставить значение RGB. Это еще более неудачно, чем другой подход, потому что вы потерпите неудачу во время компиляции, а не во время выполнения.

Обратите внимание, что если вам нужно сделать еще более сложные вещи, в том числе наличие каждого цвета, его собственная реализация абстрактного метода. Перечисления на Java действительно мощные и объектно-ориентированные, и в большинстве случаев я обнаружил, что в первую очередь я могу избежать необходимости switch.

Ответ 3

Полностью полнота компиляции корпусов коммутаторов не гарантирует завершения исполнения.

Класс с оператором switch, скомпилированным с более старой версией перечисления, может быть выполнен с новой версией enum (с большим количеством значений). Это общий случай с библиотечными зависимостями.

По причинам, подобным этим, компилятор рассматривает случай switch без default незавершенного.

Ответ 4

В небольших программах для этого нет практического применения, но подумайте о сложной системе, которая копирует большое количество файлов и разработчиков - если вы определяете enum в одном файле и используете его в другом, а затем кто-то добавляет значение в enum без обновления инструкции switch, вы найдете ее очень полезной...

Ответ 5

Да, вы должны это сделать. Вы можете изменить enum, но не меняйте switch. В будущем это приведет к ошибкам. Я считаю, что throw new IllegalArgumentException(msg) - хорошая практика.

Ответ 6

Если вы охватили все возможности с помощью различных cases и default не может быть, это классический вариант использования утверждений:

Color color = getColor(); // a method which returns a value of enum "Color"
switch (color) {
    case RED:
       // ...
       break;

    case BLUE:
       // ...
       break;

    default:
       assert false; // This cannot happen
       // or:
       throw new AssertionError("Invalid Colors enum");
}

Ответ 7

Когда констант перечисления слишком много, и вам нужно обрабатывать только несколько случаев, то default будет обрабатывать остальные константы.

Кроме того, константы перечисления являются ссылками, если ссылка еще не установлена, или null. Возможно, вам придется обращаться с такими случаями.

Ответ 8

Чтобы удовлетворить IDE и другие статические linters, я часто оставляю случай по умолчанию в виде no-op вместе с комментарием, например // Can't happen или // Unreachable

i.e, если коммутатор выполняет типичную задачу обработки всех возможных значений перечисления, либо явно, либо через провалы, тогда случай по умолчанию, вероятно, является ошибкой программиста.

В зависимости от приложения я иногда делаю утверждение в случае, чтобы защитить от ошибки программиста во время разработки. Но это имеет ограниченное значение в транспортном коде (если вы не отправляете сообщения с включенными утверждениями.)

Опять же, в зависимости от ситуации, я мог бы убедить вас в ошибке, так как это действительно неустранимая ситуация - ничего, что может сделать пользователь, будет исправлять то, что, вероятно, ошибка программиста.

Ответ 10

Помимо возможного будущего расширения перечисления, о котором многие говорили, кто-то может "улучшить" yout getColor() или переопределить его в производном классе и дать ему вернуть недопустимое значение. Конечно, компилятор должен поймать это, если только кто-то явно не запускает небезопасное литье типов...

Но плохие вещи просто происходят, и это хорошая практика не оставлять непредвиденный путь else или default неохраняемым.

Ответ 11

Я удивлен, что никто не упомянул об этом. Вы можете преобразовать int в перечисление, и оно не будет выбрасываться только потому, что значение не является одним из перечисленных значений. Это означает, между прочим, компилятор не может сказать, что все значения перечисления находятся в коммутаторе.

Даже если вы правильно пишете свой код, это действительно возникает при сериализации объектов, содержащих перечисления. Будущая версия может добавить к перечислению, а ваш дроссель кода при чтении его обратно, или кто-то, желающий создать хаос, может иметь шестнадцатеричное значение нового значения. В любом случае, сбой с коммутатора редко делает правильную вещь. Таким образом, мы бросаем дефолт, если мы не знаем лучше.

Ответ 12

Вот как я бы справился с этим, помимо значения NULL, которое приведет к исключению нулевого указателя, которое вы можете обработать.

Если Color color не NULL, он должен быть одним из синглтонов в enum Color, если вы назначаете какую-либо ссылку на объект, который не является одним из них, это приведет к ошибке выполнения.

Итак, мое решение состоит в том, чтобы учитывать значения, которые не поддерживаются.

Test Run

Test.java

public Test
{
  public static void main (String [] args)
  {
    try { test_1(null); }
    catch (NullPointerException e) { System.out.println ("NullPointerException"); }

    try { test_2(null); }
    catch (Exception e) { System.out.println(e.getMessage()); }

    try { test_1(Color.Green); }
    catch (Exception e) { System.out.println(e.getMessage()); }
  }

  public static String test_1 (Color color) throws Exception
  {
    String out = "";

    switch (color) // NullPointerException expected
    {
      case Color.Red:
        out = Red.getName();
        break;
      case Color.Blue:
        out = Red.getName();
        break;
      default:
        throw new UnsupportedArgumentException ("unsupported color: " + color.getName());
    }

    return out;
  }

.. или вы можете считать NULL как неподдерживаемый слишком

  public static String test_2 (Color color) throws Exception
  {
    if (color == null) throw new UnsupportedArgumentException ("unsupported color: NULL");
    return test_1(color);
  }
}

Color.java

enum Color
{
  Red("Red"), Blue("Blue"), Green("Green");
  private final String name;
  private Color(String n) { name = n; }
  public String getName() { return name; }
}

UnsupportedArgumentException.java

class UnsupportedArgumentException extends Exception
{
  private String message = null;

  public UnsupportedArgumentException() { super(); }

  public UnsupportedArgumentException (String message)
  {
    super(message);
    this.message = message;
  }

  public UnsupportedArgumentException (Throwable cause) { super(cause); }

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

  @Override public String getMessage() { return message; }
}

Ответ 13

В этом случае использование Assertion по умолчанию является наилучшей практикой.