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


Предположим, у меня есть перечислимый Color С 2 возможными значениями: 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");
}
13 57

13 ответов:

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

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

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

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

В отличие от таких языков, как C++ и C#, 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 на них в первую очередь.

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

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

по таким причинам компилятор считает switch без default случае неполным.

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

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

Если вы охватили все возможности с различными 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");
}

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

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

Да, это мертвый код, пока кто-то не добавит значение в перечисление, что заставит ваш оператор switch следовать принципу "fail fast" (https://en.wikipedia.org/wiki/Fail-fast)

Это может относиться к этому вопросу:как обеспечить полноту в коммутаторе перечисления во время компиляции?

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

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

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

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

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

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

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

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

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

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

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

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