Java-метод с компиляцией возвращаемого типа без оператора возврата

Вопрос 1:

Почему следующий код компилируется без инструкции return?

public int a() {
    while(true);
}

Примечание. Если я добавлю return после этого, тогда я получу Unreachable Code Error.

Вопрос 2:

С другой стороны, зачем компилируется следующий код,

public int a() {
    while(0 == 0);
}

даже если это не так.

public int a(int b) {
    while(b == b);
}

Ответ 1

Вопрос 1:

Почему следующий код компилируется без инструкции return?

public int a() 
{
    while(true);
}

Это охватывается JLS§8.4.7:

Если у метода объявлен тип возврата (§8.4.5), тогда возникает ошибка времени компиляции, если тело метода может нормально функционировать (§14.1).

Другими словами, метод с возвращаемым типом должен возвращаться только с помощью оператора return, который обеспечивает возврат значения; методу не разрешается "опускать конец его тела". См. § 14.17 для точных правил о операторах return в теле метода.

Возможно, что метод имеет тип возврата и не содержит операторов возврата. Вот один пример:

class DizzyDean {
    int pitch() { throw new RuntimeException("90 mph?!"); }
}

Так как компилятор знает, что цикл никогда не будет завершен (true всегда истинно, конечно), он знает, что функция не может "нормально вернуться" (оставьте конец своего тела), и, таким образом, все в порядке, no return.

Вопрос 2:

С другой стороны, зачем компилируется следующий код,

public int a() 
{
    while(0 == 0);
}

даже если это не так.

public int a(int b)
{
    while(b == b);
}

В случае 0 == 0 компилятор знает, что цикл никогда не завершится (что 0 == 0 всегда будет true). Но он не знает, что для b == b.

Почему бы и нет?

Компилятор понимает константные выражения (§15.28). Цитирование §15.2 - Формы выражений (потому что странно это предложение не содержится в § 15.28):

Некоторые выражения имеют значение, которое можно определить во время компиляции. Это постоянные выражения (§15.28).

В вашем примере b == b, поскольку имеется переменная, она не является константным выражением и не указывается для определения во время компиляции. Мы можем видеть, что в этом случае оно всегда будет истинным (хотя если b был double, как указывал QBrute , мы легко могли бы обмануть Double.NaN, который не ==), но JLS указывает только, что константные выражения определены во время компиляции он не позволяет компилятору попытаться оценить не константные выражения. bayou.io поднял хороший момент, почему бы и нет: если вы начнете идти по пути определения выражений с переменными во время компиляции, где вы остановитесь? b == b очевидно (er, для значений не NaN), но как насчет a + b == b + a? Или (a + b) * 2 == a * 2 + b * 2? Рисование линии в константах имеет смысл.

Итак, поскольку он не "определяет" выражение, компилятор не знает, что цикл никогда не завершится, поэтому он думает, что метод может нормально вернуться — которое ему не разрешено делать, потому что для этого нужно использовать return. Поэтому он жалуется на отсутствие return.

Ответ 2

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

  • Цикл навсегда:

    X foo() {
        for (;;);
    }
    
  • Рекурсия навсегда:

    X foo() {
        return foo();
    }
    
  • Выбрасывание исключения:

    X foo() {
        throw new Error();
    }
    

(Так как эти методы никогда не возвращают значение, тип X неважен.)

Ответ 3

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

Пример:

for(;;) покажет байт-коды:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Обратите внимание на отсутствие какого-либо возвратного байт-кода

Это никогда не срабатывает при возврате и, следовательно, не возвращает неправильный тип.

Для сравнения:

public String getBar() { 
    return bar; 
}

Вернет следующие байт-коды:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Обратите внимание на "isturn", что означает "вернуть ссылку"

Теперь, если мы сделаем следующее:

public String getBar() { 
    return 1; 
}

Вернет следующие байт-коды:

public String getBar();
  Code:
   0:   iconst_1
   1:   ireturn

Теперь мы можем видеть, что тип в определении не соответствует типу return ireturn, что означает return int.

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