Java-метод с возвращаемым типом компилируется без оператора return

228

Вопрос 1:

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

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

Обратите внимание: если я добавлю возврат через некоторое время, я получу Unreachable Code Error.

Вопрос 2:

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

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

хотя следующее не делает.

public int a(int b) {
    while(b == b);
}
Вилли Ментцель
источник
2
Не дубликат stackoverflow.com/questions/16789832/… , благодаря второй половине второго вопроса.
TJ Crowder

Ответы:

274

Вопрос 1:

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

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

Это покрыто JLS§8.4.7 :

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

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

Метод может иметь возвращаемый тип и в то же время не содержать операторов возврата. Вот один пример:

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

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

Вопрос 2:

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

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

хотя следующее не делает.

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

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

Почему нет?

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

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

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

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

TJ Crowder
источник
34

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

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

    X foo() {
        for (;;);
    }
    
  2. Навсегда

    X foo() {
        return foo();
    }
    
  3. Выкидываю исключение:

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

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

Boann
источник
8

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

Пример:

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

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

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

public String getBar() { 
    return 1; 
}

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

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

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

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

Филипп Девайн
источник