Повернення null у вигляді int дозволено з потрійним оператором, але не якщо оператор


186

Давайте розглянемо простий код Java в наступному фрагменті:

public class Main {

    private int temp() {
        return true ? null : 0;
        // No compiler error - the compiler allows a return value of null
        // in a method signature that returns an int.
    }

    private int same() {
        if (true) {
            return null;
            // The same is not possible with if,
            // and causes a compile-time error - incompatible types.
        } else {
            return 0;
        }
    }

    public static void main(String[] args) {
        Main m = new Main();
        System.out.println(m.temp());
        System.out.println(m.same());
    }
}

У цьому найпростішому коді Java temp()метод не видає помилки компілятора, хоча тип повернення функції є int, і ми намагаємось повернути значення null(через оператор return true ? null : 0;). Коли компілюється, це очевидно спричиняє виняток із часу запуску NullPointerException.

Тим НЕ менше, виявляється , що те ж саме , це не так , якщо ми представляємо трикомпонентний оператор з ifтвердженням (як в same()методі), який робить видає помилку під час компіляції! Чому?


6
Крім того, int foo = (true ? null : 0)і new Integer(null)обидва складають штрафи, друга є явною формою автобоксингу.
Ізката

2
@Izkata проблема тут для мене , щоб зрозуміти , чому компілятор намагається Autobox , nullщоб Integer... Це буде виглядати так само , як «вгадування» мені або «робити речі роботу» ...
Марселлас Wallace

1
... Гм, я думав, що у мене є відповідь, оскільки конструктору Integer (те, що, як я знайшов, документи, які використовуються для автобоксингу), дозволено приймати рядок як аргумент (який може бути нульовим). Однак вони також кажуть, що конструктор діє ідентично методу parseInt (), який би кинув NumberFormatException після отримання нуля ...
Izkata

3
@Izkata - аргумент String c'tor для Integer - це не автоматичне введення в коробку. Рядок не може бути автозавантажено цілим числом. (Функція Integer foo() { return "1"; }не збиратиметься.)
Тед Хопп

5
Класно, дізнався щось нове про термального оператора!
оксаят

Відповіді:


118

Компілятор інтерпретує nullяк нульове посилання на an Integer, застосовує правила автобоксингу / розпакування для умовного оператора (як описано в специфікації мови Java, 15.25 ) і радісно рухається далі. Це створить час NullPointerExceptionвиконання, що ви можете підтвердити, спробувавши його.


З огляду на посилання на опубліковану вами специфікацію мови Java, який пункт, на вашу думку, виконується у випадку вищезазначеного питання? Останнє (оскільки я все ще намагаюся зрозуміти capture conversionі lub(T1,T2)) ?? Також, чи реально застосувати бокс до нульового значення? Хіба це не було б як "здогадки" ??
Марселл Уоллес

´ @ Gevorg Нульовий покажчик є дійсним вказівником на кожен можливий об’єкт, тому нічого поганого там не може статися. Компілятор просто припускає, що null є цілим числом, яке потім може автоматично встановити в ящик.
Voo

1
@Gevorg - Дивіться коментар nowaq та мою відповідь на його повідомлення. Я думаю, що він обрав правильну пропозицію. lub(T1,T2)є найбільш специфічним типовим типом, поширеним в ієрархії типів T1 і T2. (Вони обоє поділяють принаймні Об'єкт, тому завжди існує найбільш конкретний тип посилання.)
Тед Хопп

8
@Gevorg - nullНЕ боксував в Integer, воно інтерпретується як посилання на Integer (посилання нульовий, але це не проблема). Жоден об'єкт Integer не будується з нуля, тому немає жодної причини для NumberFormatException.
Тед Хопп

1
@Gevorg - Якщо ви подивитесь на правила перетворення боксу та застосуєте їх до null(що не є примітивним числовим типом), застосовним пунктом є "Якщо p - значення будь-якого іншого типу, перетворення боксу еквівалентно перетворенню ідентичності ". Тож перехід боксу nullдо Integerврожайності null, не викликаючи жодного Integerконструктора.
Тед Хопп

40

Я думаю, компілятор Java трактує true ? null : 0як Integerвираз, який можна неявно перетворити int, можливо, даючи NullPointerException.

У другому випадку вираз nullмає спеціальний нульовий тип see , тому код return nullробить тип невідповідним.


2
Я припускаю, що це пов'язано з автоматичним боксом? Імовірно, перше повернення не збирається до Java 5, правда?
Майкл МакГоуан

@Michael, як видається, має місце, якщо встановити рівень відповідності Eclipse до -5.
Джонатан Фауст

@Michael: це, безумовно, схоже на автоматичний бокс (я зовсім новачок у Java, і не можу зробити більш чітке твердження - вибачте).
Влад

1
@Vlad як би кінець компілятор до інтерпретації , true ? null : 0як Integer? Шляхом автобоксингу 0спочатку ??
Марселл Уоллес

1
@Gevorg: Подивіться тут : інакше другий та третій операнди мають типи S1 та S2 відповідно. Нехай T1 - тип, який є результатом застосування перетворення боксу до S1, а T2 - тип, який є результатом застосування перетворення боксу до S2. і наступний текст.
Влад

32

Власне, все це пояснено в специфікації мови Java .

Тип умовного виразу визначається наступним чином:

  • Якщо другий і третій операнди мають один і той же тип (який може бути нульовим типом), то це тип умовного виразу.

Тому "null" у вашому (true ? null : 0)значенні отримує тип int, а потім автоматично завантажується в Integer.

Спробуйте щось подібне, щоб перевірити це, (true ? null : null)і ви отримаєте помилку компілятора.


3
Але це положення правил не застосовується: другий та третій операнди не мають одного типу.
Тед Хопп

1
Тоді відповідь здається в наступному твердженні:> В іншому випадку другий і третій операнди мають типи S1 і S2 відповідно. Нехай T1 - тип, який є результатом застосування перетворення боксу до S1, а T2 - тип, який є результатом застосування перетворення боксу до S2. Тип умовного виразу є результатом застосування перетворення захоплення (§5.1.10) до лубу (T1, T2) (§15.12.2.7).
nowaq

Я думаю, що це застосовне застереження. Потім він намагається застосувати авторозпакування для повернення intзначення з функції, що викликає NPE.
Тед Хопп

@nowaq Я теж думав про це. Тим НЕ менше, якщо ви спробуєте явно боксувати , nullщоб Integerз new Integer(null);«Нехай T1 типу , який є результат застосування перетворення по боксу S1 ...» ви отримаєте NumberFormatExceptionі це не так ...
Марселлас Wallace

@Gevorg Я думаю, оскільки виняток трапляється при боксі, ми не отримуємо жодного результату. Компілятор просто зобов'язаний генерувати код, який відповідає визначенню, яке він робить - ми просто отримуємо виняток, перш ніж ми закінчимо.
Voo

25

У випадку ifтвердження, nullпосилання не трактується як Integerпосилання, оскільки воно не бере участі у виразі, що змушує його інтерпретувати як таке. Тому помилка може бути легко виявлена ​​під час компіляції, оскільки це явніше помилка типу .

Що стосується умовного оператора, специфікація мови Java §15.25 «Умовний оператор ? :» відповідає на це чудово в правилах застосування способу перетворення типів:

  • Якщо другий і третій операнди мають один і той же тип (який може бути нульовим типом), то це тип умовного виразу.

    Не застосовується, тому що nullні int.

  • Якщо один з другого і третього операндів має тип булевого типу, а другий тип булевого типу, то тип умовного виразу булевий.

    Не застосовується, тому що ні, nullні int, booleanні Boolean.

  • Якщо один з другого та третього операндів має нульовий тип, а тип другого є еталонним типом, то тип умовного виразу є таким еталонним типом.

    Не застосовується, оскільки nullмає нульовий тип, але intне є еталонним.

  • В іншому випадку, якщо другий та третій операнди мають типи, які перетворюються (§5.1.8) в числові типи, то є кілька випадків: […]

    Застосовується: nullтрактується як конвертований у числовий тип і визначається у §5.1. 8 "Конверсія для розпакування", щоб кинути NullPointerException.

Якщо 0автозавантажено, Integerтоді компілятор виконує останній випадок "правил потрійного оператора", як описано в специфікації мови Java. Якщо це правда, мені важко повірити, що потім перейти до випадку 3 тих самих правил, який має нульовий і опорний тип, завдяки якому значення повернення термінального оператора є референтним типом (Integer) .. .
Марселл Уоллес

@Gevorg - Чому важко повірити, що потрійний оператор повертає Integer? Саме так і відбувається; NPE створюється, намагаючись розпакувати значення виразу, щоб повернути a intз функції. Змініть функцію для повернення, Integerі вона повернеться nullбез проблем.
Тед Хопп

2
@TedHopp: Геворг відповідав на попередню редакцію моєї відповіді, яка була неправильною. Ви повинні ігнорувати невідповідність.
Джон Перді

@JonPurdy "Тип, як кажуть, може бути конвертований в числовий тип, якщо це числовий тип, або це еталонний тип, який може бути перетворений на числовий тип шляхом розблокування перетворення", і я не думаю, що nullпідпадає під цю категорію . Крім того, ми б переходимо до кроку "В іншому випадку застосовується бінарне числове просування (§5.6.2) ... Зауважте, що бінарне числове просування виконує перетворення unboxing (§5.1.8) ..." для визначення типу повернення. Але конверсія для розгортання породжуватиме NPE, і це відбувається лише під час виконання, а не при спробі визначення типу потрійного оператора. Я все ще
плутаюсь

@Gevorg: Розпакування відбувається в час виконання. nullРозглядається , як якщо б це був тип int, але насправді еквівалентно throw new NullPointerException(), що все.
Джон Перді

11

Перше, що слід пам’ятати, це те, що потрійні оператори Java мають «тип», і що саме це визначатиме компілятор і враховує, незалежно від того, які фактичні / реальні типи другого або третього параметра. Залежно від декількох факторів тип потрійного оператора визначається різними способами, як показано в специфікації мови Java 15.26

У вищезазначеному питанні ми повинні розглянути останній випадок:

В іншому випадку другий і третій операнди мають типи S1 і S2 відповідно. Нехай T1 - тип, який є результатом застосування перетворення боксу до S1 , а T2 - тип, який є результатом застосування перетворення боксу до S2 . Тип умовного виразу є результатом застосування перетворення захоплення (§5.1.10) до лубу (T1, T2) (§15.12.2.7).

Це, безумовно, найскладніший випадок, коли ви подивитесь на застосування конверсії захоплення (§5.1.10), а найбільше - на луб (T1, T2) .

Простий англійською мовою і після надзвичайного спрощення ми можемо описати процес як обчислення "Найменшого загального суперкласу" (так, подумайте про LCM) другого та третього параметрів. Це дасть нам потрійний оператор "типу". Знову ж таки, те, що я щойно сказав, - це надзвичайне спрощення (розглянемо класи, які реалізують кілька загальних інтерфейсів).

Наприклад, якщо ви спробуєте наступне:

long millis = System.currentTimeMillis();
return(true ? new java.sql.Timestamp(millis) : new java.sql.Time(millis));

Ви помітите, що отриманий тип умовного виразу є java.util.Dateтим, що це "Найменший загальний суперклас" для Timestamp/ Timeпари.

Оскільки nullйого можна автокорегувати будь-чим, то "Найменший загальний надклас" - це Integerклас, і це буде тип повернення умовного виразу (потрійний оператор) вище. Значення, що повертається, буде нульовим покажчиком типу, Integerі саме це повернеться термінальним оператором.

Під час виконання, коли віртуальна машина Java розблоковує , викидається Integera NullPointerException. Це відбувається тому, що JVM намагається викликати функцію null.intValue(), де nullє результатом автобоксингу.

На мою думку (а оскільки моя думка відсутня в специфікації мови Java, багато людей вважають це неправильним), компілятор робить погану роботу в оцінці виразу у вашому запитанні. Враховуючи, що ви написали, true ? param1 : param2компілятор повинен відразу визначити, що перший параметр - null- буде повернутий, і він повинен створити помилку компілятора. Це дещо схоже на те, коли ви пишете, while(true){} etc...і компілятор скаржиться на код під циклом і позначає його Unreachable Statements.

Ваш другий випадок досить простий, і ця відповідь вже занадто довга ...;)

КОРЕКЦІЯ:

Після іншого аналізу я вважаю, що я помилявся, сказавши, що nullзначення може бути встановлено в коробці / автобоксировано до будь-чого. Якщо говорити про клас Integer, то явний бокс полягає у виклику new Integer(...)конструктора чи, можливо, Integer.valueOf(int i);(я знайшов цю версію десь). Перший кине NumberFormatException(а цього не відбувається), а другий просто не має сенсу, оскільки не intможе бути null...


1
nullУ вихідному коді OP є не боксував. Як це працює: компілятор припускає, що nullце посилання на ціле число. Використовуючи правила для потрійних типів виразів, він вирішує, що весь вираз є цілим виразом. Потім він генерує код для автоматичної скриньки 1(у випадку, якщо умова оцінюється на false). Під час виконання умова оцінюється trueтак, щоб вираження оцінювалось на null. При спробі повернути функцію a int, nullunboxed. Це потім кидає NPE. (Компілятор, можливо, оптимізує більшу частину цього заходу.)
Тед Хопп

4

Насправді, у першому випадку вираз можна оцінити, оскільки компілятор знає, що він повинен бути оцінений як an Integer, однак у другому випадку тип повернутого значення ( null) не може бути визначений, тому його неможливо скласти. Якщо ви кинете його Integer, код скомпілюватиметься.


2
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of unboxing then the returned value could be null
        //in other words I can say x could be null or new Integer(intValue) or a intValue
    }

    return (true ? null : 0);  //this will be prefectly legal null would be refrence to Integer. The concept is one the returned
    //value can be Integer 
    // then null is accepted to be a variable (-refrence variable-) of Integer
}

0

Як щодо цього:

public class ConditionalExpressionType {

    public static void main(String[] args) {

        String s = "";
        s += (true ? 1 : "") instanceof Integer;
        System.out.println(s);

        String t = "";
        t += (!true ? 1 : "") instanceof String;
        System.out.println(t);

    }

}

Вихід вірний, правдивий.

Колір затемнення кодує 1 у умовному виразі як автозахисне зображення.

Я думаю, що компілятор бачить тип повернення виразу як Object.

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