Метод Java з типом повернення компілюється без оператора return


228

Питання 1:

Чому наступний код складається без заяви про повернення?

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/… , завдяки другій половині 2-го питання.
TJ Crowder

Відповіді:


274

Питання 1:

Чому наступний код складається без заяви про повернення?

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

Це стосується JLS§8.4.7 :

Якщо метод оголошено типом повернення (§ 8.4.5), тоді виникає помилка часу компіляції, якщо тіло методу може нормально завершити (§14.1).

Іншими словами, метод з типом повернення повинен повертатися лише за допомогою оператора return, який забезпечує повернення значення; метод не дозволяє "скидати кінець свого тіла". Точні правила щодо операторів повернення в тілі методу див. У § 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.


34

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

  1. Цикл назавжди:

    X foo() {
        for (;;);
    }
    
  2. Повторюється назавжди:

    X foo() {
        return foo();
    }
    
  3. Викидання винятку:

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

(Мені здається, що про рекурсію варто подумати: Компілятор вважає, що метод поверне значення типу X(що б це не було), але це неправда, оскільки немає коду, який би не мав уявлення про те, як створити або придбати X.)


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, що означає return int.

Тож справді доводиться те, що якщо метод має шлях повернення, той шлях повинен відповідати типу повернення. Але в байт-коді є випадки, коли взагалі не генерується шлях повернення, і, отже, не порушується правило.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.