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

Почему по умолчанию требуется для включения перечисления?

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

public enum XYZ {A,B};
public static String testSwitch(XYZ xyz)
{
    switch(xyz)
    {
    case A:
        return "A";
    case B:
    //default:
        return "B";
    }
}
4b9b3361

Ответ 1

Причина, по которой вам нужно раскомментировать default, заключается в том, что ваша функция говорит, что она возвращает String, но если у вас есть только теги case, определенные для A и B, тогда функция не будет верните значение, если вы передадите что-нибудь еще. Java требует, чтобы все функции, которые заявили, что они возвращают значение, действительно возвращают значение на всех возможных путях управления, а в вашем случае компилятор не убежден, что все возможные входы имеют возвращаемое значение.

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

С технической точки зрения, я считаю, что настоящая причина заключается в том, что верификатор байт-кода JVM всегда отвергает функции, в которых есть некоторый путь управления, который падает с конца функции (см. раздел 4.9.2 спецификации JVM), и поэтому, если код должен был компилироваться, он все равно будет отклонен JVM во время выполнения. Поэтому компилятор сообщает об ошибке, указывающей на наличие проблемы.

Ответ 2

Я думаю, что это объясняется правилами определенного присваивания JLS для операторов switch (JLS 16.2.9), в которых говорится следующее:

"V [un] присваивается после оператора switch, если все следующее верно:

  • Либо есть метка по умолчанию в блоке переключателей, либо V назначается [un] после выражения переключателя.

Если мы затем применим это к условному V которое является возвращаемым значением метода, мы увидим, что если ветки по default нет, значение будет условно не назначено.

ОК... Я экстраполирую определенные правила присваивания, чтобы покрыть возвращаемые значения, и, возможно, они этого не делают.Но тот факт, что я не смог найти что-то более прямое в спецификации, не означает, что его там нет :-)


Есть еще одна (более обоснованная) причина, по которой компилятор должен выдавать ошибку. Он вытекает из правил двоичной совместимости для enum (JLS 13.4.26), которые утверждают следующее:

"Добавление или переупорядочение констант из типа enum не нарушит совместимость с уже существующими двоичными файлами".

Так как же это применимо в этом случае? Хорошо, предположим, что компилятору было позволено сделать вывод, что оператор переключателя примера OP всегда возвращал что-то. Что произойдет, если программист теперь изменит enum добавив дополнительную константу? Согласно правилам двоичной совместимости JLS, мы не нарушили двоичную совместимость. Тем не менее, метод, содержащий оператор switch теперь может (в зависимости от его аргумента) возвращать неопределенное значение. Этого нельзя допустить, поэтому переключение должно быть ошибкой компиляции.


В Java 12 были введены улучшения для переключения, включающие выражения переключения. Это сталкивается с той же проблемой с перечислениями, которые изменяются между временем компиляции и временем выполнения. Согласно JEP 354, они решают эту проблему следующим образом:

Случаи выражения switch должны быть исчерпывающими; для всех возможных значений должна быть соответствующая метка переключателя. (Очевидно, что операторы switch не обязательно должны быть исчерпывающими.)

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

Единственное, что не совсем понятно, - это то, что фактически делает неявное предложение по умолчанию. Я предполагаю, что это приведет к непроверенному исключению. (На данный момент JLS для Java 12 не обновлялся для описания новых выражений переключателей.)

Ответ 3

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

Примечание: для xyz есть третье значение, которое равно null.

public static String testSwitch(XYZ xyz) {
    if(xyz == null) return "null";
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    return xyz.getName();
}

Это тот же результат, что и

public static String testSwitch(XYZ xyz) {
     return "" + xyz;
}

Единственный способ избежать возврата - это выбросить исключение.

public static String testSwitch(XYZ xyz) {
    switch(xyz){
    case A:
        return "A";
    case B:
        return "B";
    }
    throw new AssertionError("Unknown XYZ "+xyz);
}

Ответ 4

Существует договор, согласно которому этот метод должен возвращать String, если он не выбрасывает исключение. И каждое время не ограничивается случаями, когда значение xyz равно XVZ.A или XYZ.B.

Вот еще один пример, где он obviuos, что код будет работать правильно, но где мы имеем ошибку compiletime по той же самой причине:

public boolean getTrue() {
  if (1 == 1) return true;
}

Неверно, что вы должны добавлять инструкцию по умолчанию, это правда, что вы должны вернуть значение в любое время. Поэтому либо добавьте инструкцию по умолчанию, либо добавьте оператор return после блока switch.

Ответ 5

В Java 12 вы можете использовать функцию выражения переключения предварительного просмотра (JEP-325) следующим образом:

public static String testSwitch(XYZ xyz) {
    return switch (xyz) {
        case A -> "A";
        case B -> "B";
    };
}

и вам не нужен регистр по умолчанию, если вы обрабатываете все значения перечисления в switch.

Обратите внимание, что для использования функции предварительного просмотра вам нужно передать --enable-preview --source 12 опций в javac и java

Ответ 6

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

Ответ 7

default: throw new AssertionError();