Каково использование значения по умолчанию, когда переключатель предназначен для перечисления?
Предположим, что у меня есть перечисление 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");
}
Ответы
Ответ 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, если коммутатор выполняет типичную задачу обработки всех возможных значений перечисления, либо явно, либо через провалы, тогда случай по умолчанию, вероятно, является ошибкой программиста.
В зависимости от приложения я иногда делаю утверждение в случае, чтобы защитить от ошибки программиста во время разработки. Но это имеет ограниченное значение в транспортном коде (если вы не отправляете сообщения с включенными утверждениями.)
Опять же, в зависимости от ситуации, я мог бы убедить вас в ошибке, так как это действительно неустранимая ситуация - ничего, что может сделать пользователь, будет исправлять то, что, вероятно, ошибка программиста.
Ответ 9
Да, это мертвый код, пока кто-то не добавит значение в перечисление, которое сделает ваш оператор switch следующим принципу "fail fast" (https://en.wikipedia.org/wiki/Fail-fast)
Это может быть связано с этим вопросом: Как обеспечить полноту в перечислительном коммутаторе во время компиляции?
Ответ 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 по умолчанию является наилучшей практикой.