Metode Java dengan kompilasi tipe kembali tanpa pernyataan kembali

228

Pertanyaan 1:

Mengapa kode berikut dikompilasi tanpa memiliki pernyataan pengembalian?

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

Perhatikan: Jika saya menambahkan kembali setelah beberapa saat maka saya mendapatkan Unreachable Code Error.

Pertanyaan 2:

Di sisi lain, mengapa kode berikut dikompilasi,

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

meskipun berikut ini tidak.

public int a(int b) {
    while(b == b);
}
Willi Mentzel
sumber
2
Bukan duplikat dari stackoverflow.com/questions/16789832/… , terima kasih untuk bagian kedua dari pertanyaan kedua.
TJ Crowder

Jawaban:

274

Pertanyaan 1:

Mengapa kode berikut dikompilasi tanpa memiliki pernyataan pengembalian?

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

Ini dicakup oleh JLS§8.4.7 :

Jika suatu metode dinyatakan memiliki tipe kembali (§8.4.5), maka kesalahan waktu kompilasi terjadi jika tubuh metode dapat menyelesaikan secara normal (§14.1).

Dengan kata lain, metode dengan tipe pengembalian harus kembali hanya dengan menggunakan pernyataan pengembalian yang memberikan pengembalian nilai; metode ini tidak diperbolehkan untuk "menurunkan ujung tubuhnya". Lihat §14.17 untuk aturan yang tepat tentang pernyataan pengembalian dalam tubuh metode.

Dimungkinkan bagi suatu metode untuk memiliki tipe pengembalian dan tidak mengandung pernyataan pengembalian. Ini salah satu contohnya:

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

Karena kompiler tahu bahwa loop tidak akan pernah berhenti ( trueselalu benar, tentu saja), ia tahu fungsi tidak dapat "kembali secara normal" (jatuhkan ujung tubuhnya), dan dengan demikian tidak apa-apa bahwa tidak ada return.

Pertanyaan 2:

Di sisi lain, mengapa kode berikut dikompilasi,

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

meskipun berikut ini tidak.

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

Dalam 0 == 0kasus ini, kompiler tahu bahwa loop tidak akan pernah berhenti (itu 0 == 0akan selalu benar). Tapi untuk itu tidak tahu b == b.

Kenapa tidak?

Kompiler memahami ekspresi konstan (§15.28) . Mengutip §15.2 - Bentuk Ekspresi (karena anehnya kalimat ini tidak ada di §15.28) :

Beberapa ekspresi memiliki nilai yang dapat ditentukan pada waktu kompilasi. Ini adalah ekspresi konstan (§15.28).

Dalam b == bcontoh Anda , karena ada variabel yang terlibat, itu bukan ekspresi konstan dan tidak ditentukan untuk ditentukan pada waktu kompilasi. Kita bisa melihat bahwa itu selalu akan menjadi benar dalam kasus ini (meskipun jika badalah double, sebagai QBrute menunjukkan , kita dapat dengan mudah tertipu oleh Double.NaN, yang tidak ==sendiri ), tapi JLS hanya menetapkan bahwa ekspresi konstan ditentukan pada waktu kompilasi , itu tidak memungkinkan kompiler untuk mencoba mengevaluasi ekspresi yang tidak konstan. bayou.io mengemukakan suatu alasan mengapa tidak: Jika Anda mulai mencoba menentukan ekspresi yang melibatkan variabel pada waktu kompilasi, di mana Anda berhenti? b == bjelas (eh, untuk non-NaNnilai), tetapi bagaimana dengan a + b == b + a? Atau (a + b) * 2 == a * 2 + b * 2? Menggambar garis pada konstanta masuk akal.

Jadi karena ia tidak "menentukan" ekspresi, kompiler tidak tahu bahwa loop tidak akan pernah berhenti, jadi ia berpikir metode tersebut dapat kembali secara normal - yang tidak diperbolehkan untuk dilakukan, karena itu diperlukan untuk digunakan return. Jadi itu mengeluh tentang kurangnya a return.

TJ Crowder
sumber
34

Mungkin menarik untuk memikirkan jenis metode pengembalian bukan sebagai janji untuk mengembalikan nilai dari jenis yang ditentukan, tetapi sebagai janji untuk tidak mengembalikan nilai yang bukan dari jenis yang ditentukan. Jadi, jika Anda tidak pernah mengembalikan apa pun, Anda tidak melanggar janji, dan salah satu dari berikut ini sah:

  1. Looping selamanya:

    X foo() {
        for (;;);
    }
  2. Berulang selamanya:

    X foo() {
        return foo();
    }
  3. Membuang pengecualian:

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

(Saya menemukan rekursi yang menyenangkan untuk dipikirkan: Kompilator percaya bahwa metode ini akan mengembalikan nilai tipe X(apa pun itu), tetapi itu tidak benar, karena tidak ada kode yang memiliki ide bagaimana membuat atau pengadaan X.)

Boann
sumber
8

Melihat kode byte, jika apa yang dikembalikan tidak sesuai dengan definisi, Anda akan menerima kesalahan kompilasi.

Contoh:

for(;;) akan menampilkan bytecodes:

L0
    LINENUMBER 6 L0
    FRAME SAME
    GOTO L0

Perhatikan kurangnya bytecode pengembalian

Ini tidak pernah mencapai pengembalian, dan dengan demikian tidak mengembalikan jenis yang salah.

Sebagai perbandingan, metode seperti:

public String getBar() { 
    return bar; 
}

Akan mengembalikan bytecode berikut:

public java.lang.String getBar();
    Code:
      0:   aload_0
      1:   getfield        #2; //Field bar:Ljava/lang/String;
      4:   areturn

Perhatikan "areturn" yang berarti "kembalikan referensi"

Sekarang jika kita melakukan hal berikut:

public String getBar() { 
    return 1; 
}

Akan mengembalikan bytecode berikut:

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

Sekarang kita dapat melihat bahwa tipe dalam definisi tidak cocok dengan tipe pengembalian ireturn, yang berarti return int.

Jadi sebenarnya apa yang turun adalah bahwa jika metode ini memiliki jalur kembali, jalur itu harus cocok dengan tipe pengembalian. Tetapi ada beberapa contoh dalam bytecode di mana tidak ada jalan kembali yang dihasilkan sama sekali, dan dengan demikian tidak ada pelanggaran aturan.

Philip Devine
sumber