Trudny Operator trójkowy w Javie-autoboxing

Spójrzmy na prosty kod Javy w poniższym fragmencie:

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());
    }
}

W tym najprostszym kodzie Javy, metoda temp() nie powoduje błędu kompilatora, mimo że zwracanym typem funkcji jest int, a my staramy się zwrócić wartość null (poprzez polecenie return true ? null : 0;). Po skompilowaniu powoduje to oczywiście wyjątek czasu wykonania NullPointerException.

Wydaje się jednak, że to samo jest złe, jeśli reprezentujemy operator trójkowy za pomocą if twierdzenia (jak w same() metoda), która powoduje błąd w czasie kompilacji! Dlaczego?

Author: Tiny, 2011-11-11

8 answers

Kompilator interpretuje null jako zerowe odniesienie do Integer, stosuje zasady autoboxingu/unboxingu dla operatora warunkowego (opisane w specyfikacji języka Java , 15.25) i porusza się szczęśliwie dalej. Spowoduje to wygenerowanie NullPointerException w czasie wykonywania, co można potwierdzić, próbując go.

 112
Author: Ted Hopp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-07-18 06:46:08

Myślę, że kompilator Javy interpretuje true ? null : 0 jako wyrażenie Integer, które może być w domyśle przekonwertowane na int, ewentualnie dając NullPointerException.

W drugim przypadku wyrażenie {[4] } jest typu specjalnego null Zobacz , więc kod return null powoduje niedopasowanie typu.

 39
Author: Vlad,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-02 00:59:53

Właściwie wszystko to zostało wyjaśnione w specyfikacji języka Java .

Typ wyrażenia warunkowego określa się następująco:

  • jeśli drugi i trzeci operand mają ten sam typ (który może być typem null), to jest to typ wyrażenia warunkowego.

Dlatego "null" w (true ? null : 0) otrzymuje typ int i jest automatycznie przypisywany do liczby całkowitej.

Spróbuj czegoś takiego, aby to zweryfikować (true ? null : null) I ty spowoduje błąd kompilatora.

 32
Author: nowaq,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-02 00:57:30

W przypadku twierdzenia if odniesienie null nie jest traktowane jako odniesienie Integer, ponieważ nie uczestniczy w wyrażeniu , które wymusza jego interpretację jako takie. Dlatego błąd może być łatwo wychwycony podczas kompilacji, ponieważ jest to wyraźniejszy błąd typu .

Jeśli chodzi o operator warunkowy, specyfikacja języka Java §15.25 "operator warunkowy ? :" odpowiada na to ładnie w regułach konwersji typu

  • jeśli drugi i trzeci operand mają ten sam typ (który może być null type), wtedy jest to typ wyrażenia warunkowego.

    Nie dotyczy, ponieważ null nie jest int.

  • Jeśli jeden z drugich i trzecich operandów jest typu boolean i typu inne jest typu Boolean, wtedy typem wyrażenia warunkowego jest boolean.

    Nie dotyczy, ponieważ ani null ani int jest boolean lub Boolean.

  • Jeśli jeden z drugich i trzecich operandów jest typu null i typu inny jest typem odniesienia, wtedy typ wyrażenia warunkowego jest taki, że Typ odniesienia.

    nie ma zastosowania, ponieważ {[1] } jest typu null, ale int nie jest typem odniesienia.

  • W przeciwnym razie, jeśli drugi i trzeci operand mają typy, które są konwertowalne (§5.1.8) do typów liczbowych, wtedy jest kilka przypadków: [...]

    stosuje się: {[1] } jest traktowana jako zamienna dla typu liczbowego i jest zdefiniowana w §5.1.8 "Konwersja Unboxing" do wyrzucenia NullPointerException.
 25
Author: Jon Purdy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-12 19:07:56

Pierwszą rzeczą, o której należy pamiętać jest to, że operatory trójdzielne Javy mają "typ" i to właśnie kompilator określi i rozważy bez względu na to, jakie są rzeczywiste/rzeczywiste typy drugiego lub trzeciego parametru. W zależności od kilku czynników Typ operatora trójdzielnego jest określany na różne sposoby, jak pokazano w specyfikacji języka Java 15.26

W powyższym pytaniu powinniśmy rozważyć ostatni przypadek:

Inaczej drugi i trzeci operand to odpowiednio typy S1 i S2 . Niech T1 będzie typem wynikającym z zastosowania konwersji boksu do S1 , a T2 będzie typem wynikającym z zastosowania konwersji boksu do S2 . Typ wyrażenia warunkowego jest wynikiem zastosowania konwersji przechwytywania (§5.1.10) do lub (T1, T2) (§15.12.2.7).

Jest to zdecydowanie najbardziej skomplikowana sprawa, gdy spojrzysz na zastosowanie konwersji przechwytywania (§5.1.10) i przede wszystkim na lub (T1, T2).

W prostym języku angielskim i po skrajnym uproszczeniu możemy opisać ten proces jako obliczanie "najmniej wspólnej superklasy" (tak, pomyśl o LCM) drugiego i trzeciego parametru. To da nam trójkowy operator "Typ". Ponownie, to, co właśnie powiedziałem, jest skrajnym uproszczeniem (rozważ klasy, które implementują wiele wspólnych interfejsów).

Na przykład, jeśli spróbuj:

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

Zauważysz, że wynikowy typ wyrażenia warunkowego to java.util.Date, ponieważ jest to "najmniej powszechna Klasa nadrzędna" dla Timestamp/Time para.

Ponieważ null może być automatycznie przypisana do czegokolwiek, "najmniej powszechną klasą nadrzędną" jest klasa Integer i będzie to typ zwracany wyrażenia warunkowego (operator trójkowy) powyżej. Zwracana wartość będzie wtedy wskaźnikiem null typu Integer i to jest to, co zostanie zwrócone przez Trójnik centrala.

W czasie wykonywania, gdy maszyna wirtualna Java rozpakowuje pola Integer a NullPointerException jest wyrzucana. Dzieje się tak, ponieważ JVM próbuje wywołać funkcję null.intValue(), gdzie {[4] } jest wynikiem autoboxingu.

Moim zdaniem (a ponieważ moja opinia nie znajduje się w specyfikacji języka Java, Wiele osób i tak uzna to za złe) kompilator źle ocenia wyrażenie w twoim pytaniu. Biorąc pod uwagę, że napisałeś true ? param1 : param2 kompilator powinien od razu stwierdzić, że pierwszy parametr -null- zostanie zwrócony i powinien wygenerować błąd kompilatora. Jest to nieco podobne do tego, gdy piszesz while(true){} etc..., a kompilator narzeka na kod znajdujący się pod pętlą i zaznacza go symbolem Unreachable Statements.

Twój drugi przypadek jest dość prosty, a ta odpowiedź jest już zbyt długa... ;)

Korekta:

Po kolejnej analizie uważam, że myliłem się mówiąc, że wartość null może być boxed / autoboxed do czegokolwiek. Mówiąc o class Integer, explicit boxing polega na wywołaniu konstruktora new Integer(...) lub może Integer.valueOf(int i); (gdzieś znalazłem tę wersję). Pierwszy rzuci NumberFormatException (i tak się nie dzieje) , podczas gdy drugi po prostu nie ma sensu, ponieważ int nie może być null...

 11
Author: Gevorg,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-02-02 01:08:56

Właściwie w pierwszym przypadku wyrażenie może być obliczone, ponieważ kompilator wie, że musi być obliczone jako Integer, jednak w drugim przypadku nie można określić typu zwracanej wartości (null), więc nie można go skompilować. Jeśli wrzucisz go do Integer, Kod się skompiluje.

 4
Author: GeT,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-10-21 19:46:21
private int temp() {

    if (true) {
        Integer x = null;
        return x;// since that is fine because of auto-boxing 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
}
 2
Author: YouYou,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-01-26 23:23:46

A może tak:

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);

    }

}

Wyjście jest prawdziwe, prawdziwe.

Kolor Eclipse koduje 1 w wyrażeniu warunkowym jako autoboxed.

Domyślam się, że kompilator widzi zwracany typ wyrażenia jako obiekt.

 0
Author: Jon,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-09-08 19:40:54