Mengembalikan nol sebagai int diizinkan dengan operator ternary tetapi tidak jika pernyataan

186

Mari kita lihat kode Java sederhana dalam cuplikan berikut:

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

Dalam kode Java yang paling sederhana ini, temp()metode ini tidak mengeluarkan kesalahan kompiler meskipun jenis fungsi yang dikembalikan adalah int, dan kami mencoba mengembalikan nilainya null(melalui pernyataan return true ? null : 0;). Saat dikompilasi, ini jelas menyebabkan pengecualian waktu berjalan NullPointerException.

Namun, tampaknya hal yang sama salah jika kita mewakili operator ternary dengan ifpernyataan (seperti dalam same()metode), yang memang mengeluarkan kesalahan waktu kompilasi! Mengapa?

Singa
sumber
6
Juga, int foo = (true ? null : 0)dan new Integer(null)keduanya mengkompilasi dengan baik, yang kedua adalah bentuk eksplisit dari autoboxing.
Izkata
2
@Izkata masalahnya di sini adalah bagi saya untuk memahami mengapa kompiler mencoba untuk autobox nullke Integer... Itu akan terlihat seperti "menebak" kepada saya atau "membuat semuanya bekerja" ...
Marsellus Wallace
1
... Huhm, saya pikir saya punya jawaban di sana, sebagai konstruktor Integer (apa yang saya katakan katakan digunakan untuk autoboxing) diizinkan untuk mengambil String sebagai argumen (yang bisa nol). Namun, mereka juga mengatakan bahwa tindakan konstruktor identik dengan metode parseInt (), yang akan melempar NumberFormatException setelah melewati nol ...
Izkata
3
@Izkata - argumen String c'tor untuk Integer bukan oepration yang otomatis. String tidak dapat diautobox ke Integer. (Fungsi Integer foo() { return "1"; }tidak akan dikompilasi.)
Ted Hopp
5
Keren, pelajari sesuatu yang baru tentang operator ternary!
oksayt

Jawaban:

118

Compiler mengartikan nullsebagai referensi nol ke Integer, menerapkan aturan autoboxing / unboxing untuk operator bersyarat (seperti yang dijelaskan dalam Spesifikasi Bahasa Jawa, 15.25 ), dan bergerak dengan senang hati. Ini akan menghasilkan NullPointerExceptionwaktu berjalan, yang dapat Anda konfirmasikan dengan mencobanya.

Ted Hopp
sumber
Mengingat tautan ke Spesifikasi Bahasa Jawa yang Anda poskan, menurut Anda poin mana yang dieksekusi dalam kasus pertanyaan di atas? Yang terakhir (karena saya masih berusaha mengerti capture conversiondan lub(T1,T2)) ?? Juga, Apakah benar-benar mungkin untuk menerapkan tinju ke nilai nol? Bukankah ini seperti "menebak" ??
Marsellus Wallace
´ @ Gevorg Pointer null adalah pointer yang valid untuk setiap objek yang memungkinkan sehingga tidak ada hal buruk yang dapat terjadi di sana. Compiler hanya berasumsi bahwa null adalah Integer yang kemudian dapat autobox ke int.
Voo
1
@Gevorg - Lihat komentar nowaq dan respons saya terhadap posnya. Saya pikir dia memilih klausa yang benar. lub(T1,T2)adalah tipe referensi paling spesifik yang sama dalam tipe hierarki T1 dan T2. (Mereka berdua berbagi setidaknya Object, jadi selalu ada jenis referensi yang paling spesifik.)
Ted Hopp
8
@ Gevorg - nulltidak dimasukkan ke dalam bilangan bulat , itu ditafsirkan sebagai referensi ke bilangan bulat (referensi nol, tapi itu bukan masalah). Tidak ada objek Integer yang dibangun dari nol, jadi tidak ada alasan untuk NumberFormatException.
Ted Hopp
1
@Gevorg - Jika Anda melihat aturan konversi tinju , dan menerapkannya pada null(yang bukan tipe numerik primitif), klausa yang berlaku adalah "Jika p adalah nilai dari jenis lain, konversi tinju setara dengan konversi identitas ". Jadi konversi tinju nulluntuk Integermenghasilkan null, tanpa memanggil Integerkonstruktor.
Ted Hopp
40

Saya pikir, kompiler Java mengartikan true ? null : 0sebagai Integerekspresi, yang dapat secara implisit dikonversi menjadi int, mungkin memberi NullPointerException.

Untuk kasus kedua, ekspresi nulladalah khusus jenis nol lihat , sehingga kode return nullmembuat jenis ketidakcocokan.

Vlad
sumber
2
Saya menganggap ini terkait dengan tinju otomatis? Agaknya pengembalian pertama tidak akan dikompilasi sebelum Java 5, kan?
Michael McGowan
@Michael yang tampaknya menjadi masalah jika Anda mengatur tingkat kepatuhan Eclipse ke pra-5.
Jonathon Faust
@ Michael: ini jelas terlihat seperti tinju otomatis (saya cukup baru di Jawa, dan tidak dapat membuat pernyataan yang lebih jelas - maaf).
Vlad
1
@ Vlad bagaimana akhirnya kompiler menafsirkan true ? null : 0sebagai Integer? Dengan autoboxing 0dulu ??
Marsellus Wallace
1
@ Gevorg: Lihat di sini : Jika tidak, operan kedua dan ketiga masing-masing adalah tipe S1 dan S2. Misalkan T1 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S1, dan biarkan T2 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S2. dan teks berikut.
Vlad
32

Sebenarnya, itu semua dijelaskan dalam Spesifikasi Bahasa Jawa .

Jenis ekspresi kondisional ditentukan sebagai berikut:

  • Jika operan kedua dan ketiga memiliki tipe yang sama (yang mungkin merupakan tipe nol), maka itu adalah tipe ekspresi kondisional.

Oleh karena itu "null" di Anda (true ? null : 0)mendapat tipe int dan kemudian diautobox ke Integer.

Coba sesuatu seperti ini untuk memverifikasi ini (true ? null : null)dan Anda akan mendapatkan kesalahan kompiler.

sekarang
sumber
3
Tapi klausul aturan itu tidak berlaku: operan kedua dan ketiga tidak memiliki tipe yang sama.
Ted Hopp 11/11
1
Maka jawabannya tampaknya dalam pernyataan berikut:> Jika tidak, operan kedua dan ketiga masing-masing adalah tipe S1 dan S2. Misalkan T1 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S1, dan biarkan T2 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S2. Jenis ekspresi kondisional adalah hasil dari penerapan konversi tangkap (§5.1.10) ke lub (T1, T2) (§15.12.2.7).
nowaq
Saya pikir itulah klausa yang berlaku. Kemudian ia mencoba menerapkan penghapusan otomatis untuk mengembalikan intnilai dari fungsi, yang menyebabkan NPE.
Ted Hopp
@nowaq Saya pikir ini juga. Namun, jika Anda mencoba untuk secara eksplisit kotak nulluntuk Integerdengan new Integer(null);"Let T1 menjadi tipe yang hasil dari penerapan konversi tinju ke S1 ..." Anda akan mendapatkan NumberFormatExceptiondan ini tidak terjadi ...
Marsellus Wallace
@Gevorg Saya pikir karena pengecualian terjadi ketika melakukan tinju kita tidak mendapatkan hasil APAPUN di sini. Kompiler hanya berkewajiban untuk menghasilkan kode yang mengikuti definisi yang dilakukannya - kita hanya mendapatkan pengecualian sebelum kita selesai.
Voo
25

Dalam kasus ifpernyataan, nullreferensi tidak diperlakukan sebagai Integerreferensi karena tidak berpartisipasi dalam ekspresi yang memaksanya untuk ditafsirkan seperti itu. Oleh karena itu kesalahan dapat dengan mudah ditangkap pada waktu kompilasi karena lebih jelas kesalahan jenis .

Adapun operator kondisional, Spesifikasi Bahasa Jawa §15.25 "Operator Bersyarat ? :" menjawab ini dengan baik dalam aturan untuk bagaimana konversi tipe diterapkan:

  • Jika operan kedua dan ketiga memiliki tipe yang sama (yang mungkin merupakan tipe nol), maka itu adalah tipe ekspresi kondisional.

    Tidak berlaku karena nulltidak int.

  • Jika salah satu dari operan kedua dan ketiga adalah tipe boolean dan tipe lainnya adalah tipe Boolean, maka jenis ekspresi kondisional adalah boolean.

    Tidak berlaku karena tidak nulljuga intadalah booleanatau Boolean.

  • Jika salah satu dari operan kedua dan ketiga adalah tipe nol dan tipe yang lain adalah tipe referensi, maka tipe ekspresi kondisional adalah tipe referensi tersebut.

    Tidak berlaku karena nulladalah tipe null, tetapi intbukan tipe referensi.

  • Kalau tidak, jika operan kedua dan ketiga memiliki tipe yang dapat dikonversi (§5.1.8) ke tipe numerik, maka ada beberapa kasus: [...]

    Berlaku: nulldiperlakukan sebagai konversi ke tipe numerik, dan didefinisikan dalam §5.1. 8 "Konversi Penghapusan Kotak" untuk melempar a NullPointerException.
Jon Purdy
sumber
Jika 0diautobox Integermaka kompiler sedang menjalankan kasus terakhir dari "aturan operator ternary" seperti yang dijelaskan dalam Spesifikasi Bahasa Jawa. Jika itu benar, maka sulit bagi saya untuk percaya bahwa itu kemudian akan melompat ke kasus 3 dari aturan yang sama yaitu memiliki nol dan tipe referensi yang membuat nilai kembali dari operator ternary menjadi tipe referensi (Integer) .. .
Marsellus Wallace
@ Gevorg - Mengapa sulit untuk percaya bahwa operator ternary mengembalikan sebuah Integer? Itulah tepatnya yang terjadi; NPE sedang dibuat dengan mencoba membuka kotak nilai ekspresi untuk mengembalikan suatu intdari fungsi. Ubah fungsi untuk mengembalikan Integerdan itu akan kembali nulltanpa masalah.
Ted Hopp
2
@TedHopp: Gevorg merespons revisi sebelumnya atas jawaban saya, yang salah. Anda harus mengabaikan perbedaannya.
Jon Purdy
@JonPurdy "Suatu tipe dikatakan dapat dikonversi ke tipe numerik jika tipe numerik, atau tipe referensi yang dapat dikonversi ke tipe numerik dengan membuka kotak konversi" dan saya tidak berpikir itu nulltermasuk dalam kategori ini . Selain itu, kita akan masuk ke langkah "Jika tidak, promosi numerik biner (§5.6.2) diterapkan ... Perhatikan bahwa promosi numerik biner melakukan konversi unboxing (§5.1.8) ..." untuk menentukan jenis pengembalian. Tetapi konversi unboxing akan menghasilkan NPE dan ini hanya terjadi pada saat runtime dan tidak ketika mencoba untuk menentukan jenis operator ternary. Saya masih bingung ..
Marsellus Wallace
@Gevorg: Pembongkaran terjadi saat runtime. Itu nulldiperlakukan seolah-olah itu jenis int, tetapi sebenarnya setara dengan throw new NullPointerException(), itu saja.
Jon Purdy
11

Hal pertama yang perlu diingat adalah bahwa operator ternary Java memiliki "tipe", dan bahwa inilah yang akan ditentukan dan dipertimbangkan oleh kompilator apa pun tipe aktual / nyata dari parameter kedua atau ketiga. Tergantung pada beberapa faktor, tipe operator ternary ditentukan dengan cara berbeda seperti yang diilustrasikan dalam Spesifikasi Bahasa Jawa 15.26

Dalam pertanyaan di atas kita harus mempertimbangkan kasus terakhir:

Jika tidak, operan kedua dan ketiga adalah tipe S1 dan S2 masing-masing. Misalkan T1 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S1 , dan biarkan T2 adalah tipe yang dihasilkan dari penerapan konversi tinju ke S2 . Jenis ekspresi kondisional adalah hasil dari penerapan konversi tangkap (§5.1.10) ke lub (T1, T2) (§15.12.2.7).

Sejauh ini, ini adalah kasus yang paling rumit setelah Anda menerapkan konversi tangkapan (§5.1.10) dan yang paling utama di lub (T1, T2) .

Dalam bahasa Inggris biasa dan setelah penyederhanaan ekstrim kita dapat menggambarkan proses sebagai penghitungan "Least Common Superclass" (ya, pikirkan LCM) dari parameter kedua dan ketiga. Ini akan memberi kita "tipe" operator ternary. Sekali lagi, apa yang baru saja saya katakan adalah penyederhanaan ekstrem (pertimbangkan kelas yang mengimplementasikan beberapa antarmuka umum).

Misalnya, jika Anda mencoba yang berikut:

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

Anda akan melihat bahwa jenis ekspresi kondisional yang dihasilkan adalah java.util.Date"Least Common Superclass" untuk Timestamp/ Timepair.

Karena nulldapat diautobox ke apa pun, "Least Common Superclass" adalah Integerkelas dan ini akan menjadi tipe kembalinya ekspresi kondisional (operator ternary) di atas. Nilai kembali kemudian akan menjadi pointer nol dari jenis Integerdan itulah yang akan dikembalikan oleh operator ternary.

Saat runtime, ketika Java Virtual Machine membuka kotak Integera NullPointerExceptiondilemparkan. Ini terjadi karena JVM mencoba untuk memanggil fungsi null.intValue(), di mana nulladalah hasil dari autoboxing.

Menurut pendapat saya (dan karena pendapat saya tidak ada dalam Spesifikasi Bahasa Jawa, banyak orang akan menganggapnya salah) penyusun melakukan pekerjaan yang buruk dalam mengevaluasi ekspresi dalam pertanyaan Anda. Mengingat bahwa Anda menulis true ? param1 : param2kompiler harus segera menentukan bahwa parameter pertama - null- akan dikembalikan dan itu akan menghasilkan kesalahan kompiler. Ini agak mirip dengan ketika Anda menulis while(true){} etc...dan kompiler mengeluh tentang kode di bawah loop dan menandainya Unreachable Statements.

Kasus kedua Anda cukup mudah dan jawaban ini sudah terlalu lama ...;)

KOREKSI:

Setelah analisis lain, saya percaya bahwa saya salah mengatakan bahwa suatu nullnilai dapat dikotak / diautobosisikan ke apa saja. Berbicara tentang Integer kelas, tinju eksplisit terdiri dari memanggil new Integer(...)konstruktor atau mungkin Integer.valueOf(int i);(saya menemukan versi ini di suatu tempat). Yang pertama akan melempar NumberFormatException(dan ini tidak terjadi) sementara yang kedua tidak akan masuk akal karena inttidak bisa null...

Marsellus Wallace
sumber
1
The nulldalam kode asli OP tidak kotak. Cara kerjanya adalah: kompiler mengasumsikan bahwa nullreferensi ke Integer. Menggunakan aturan untuk tipe ekspresi ternary, itu memutuskan seluruh ekspresi adalah ekspresi Integer. Ini kemudian menghasilkan kode untuk autobox 1(jika kondisi mengevaluasi false). Selama eksekusi, kondisi dievaluasi truesehingga ekspresi dievaluasi null. Ketika mencoba mengembalikan suatu intdari fungsi, itu nulltidak dikotak. Itu kemudian melempar NPE. (Kompilator mungkin mengoptimalkan sebagian besar dari ini.)
Ted Hopp
4

Sebenarnya, dalam kasus pertama ekspresi dapat dievaluasi, karena kompiler tahu, bahwa itu harus dievaluasi sebagai Integer, namun dalam kasus kedua jenis nilai pengembalian ( null) tidak dapat ditentukan, sehingga tidak dapat dikompilasi. Jika Anda melemparkannya ke Integer, kode akan dikompilasi.

Dapatkan
sumber
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
}
Youans
sumber
0

Bagaimana dengan ini:

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

    }

}

Outputnya benar, benar.

Kode warna gerhana 1 dalam ekspresi bersyarat sebagai diautobox.

Dugaan saya adalah kompiler melihat tipe pengembalian ekspresi sebagai Object.

Jon
sumber