Mengapa mengubah variabel yang dikembalikan dalam blok akhirnya tidak mengubah nilai balik?

146

Saya memiliki kelas Java sederhana seperti yang ditunjukkan di bawah ini:

public class Test {

    private String s;

    public String foo() {
        try {
            s = "dev";
            return s;
        } 
        finally {
            s = "override variable s";
            System.out.println("Entry in finally Block");  
        }
    }

    public static void main(String[] xyz) {
        Test obj = new Test();
        System.out.println(obj.foo());
    }
}

Dan output dari kode ini adalah ini:

Entry in finally Block
dev  

Mengapa stidak diganti dalam finallyblok, belum mengontrol hasil cetak?

Pengembang
sumber
11
Anda harus meletakkan pernyataan kembali di blok terakhir, ulangi bersama saya, akhirnya blok selalu dieksekusi
Juan Antonio Gomez Moriano
1
di coba blokir kembali s
Devendra
6
Urutan pernyataan penting. Anda akan kembali ssebelum Anda mengubah nilainya.
Peter Lawrey
6
Namun, Anda dapat mengembalikan nilai baru di finallyblok , tidak seperti di C # (yang tidak dapat Anda)
Alvin Wong

Jawaban:

167

The tryblok Rampungkan dengan pelaksanaan returnpernyataan dan nilai spada saat returnpernyataan mengeksekusi adalah nilai yang dikembalikan oleh metode ini. Fakta bahwa finallyklausa kemudian mengubah nilai s(setelah returnpernyataan selesai) tidak (pada saat itu) mengubah nilai kembali.

Perhatikan bahwa di atas berkaitan dengan perubahan nilai situ sendiri di finallyblok, bukan ke objek yang sdireferensikan. Jika sreferensi ke objek yang bisa berubah (yang Stringtidak) dan isi objek diubah di finallyblok, maka perubahan itu akan terlihat pada nilai yang dikembalikan.

Aturan terperinci untuk bagaimana semua ini beroperasi dapat ditemukan di Bagian 14.20.2 dari Spesifikasi Bahasa Jawa . Perhatikan bahwa eksekusi returnpernyataan diperhitungkan sebagai penghentian mendadak tryblok (bagian mulai " Jika eksekusi blok coba selesai tiba-tiba karena alasan lain R .... " berlaku). Lihat Bagian 14.17 dari JLS untuk alasan mengapa suatu returnpernyataan tiba-tiba merupakan penghentian blok.

Dengan perincian lebih lanjut: jika tryblok dan finallyblok try-finallypernyataan berakhir tiba-tiba karena returnpernyataan, maka aturan berikut dari §14.20.2 berlaku:

Jika eksekusi tryblok selesai tiba-tiba karena alasan lain R [selain melempar pengecualian], maka finallyblok dieksekusi, dan kemudian ada pilihan:

  • Jika finallyblok selesai secara normal, maka trypernyataan selesai tiba-tiba karena alasan R.
  • Jika finallyblok selesai tiba-tiba karena alasan S, maka trypernyataan lengkap tiba-tiba karena alasan S (dan alasan R dibuang).

Hasilnya adalah bahwa returnpernyataan di finallyblok menentukan nilai kembali seluruh try-finallypernyataan, dan nilai yang dikembalikan dari tryblok dibuang. Hal serupa terjadi dalam try-catch-finallypernyataan jika tryblok melempar pengecualian, ditangkap oleh catchblok, dan baik catchblok maupun finallyblok memiliki returnpernyataan.

Ted Hopp
sumber
Jika saya menggunakan kelas StringBuilder bukannya String daripada menambahkan beberapa nilai pada akhirnya blok, itu mengubah nilai return.why?
Devendra
6
@ ev - Saya membahasnya di paragraf kedua dari jawaban saya. Dalam situasi yang Anda gambarkan, finallyblok tidak mengubah objek yang dikembalikan (yang StringBuilder) tetapi dapat mengubah internal objek. The finallymengeksekusi blok sebelum metode benar-benar kembali (meskipun returnpernyataan telah selesai), sehingga perubahan itu terjadi sebelum kode memanggil melihat nilai yang dikembalikan.
Ted Hopp
coba dengan List, Anda mendapatkan perilaku yang sama seperti StringBuilder.
Yogesh Prajapati
1
@yogeshprajapati - Yep. Hal yang sama berlaku dengan nilai kembali bisa berubah ( StringBuilder, List, Set, iklan nauseum): jika Anda mengubah isi dalam finallyblok, maka perubahan tersebut terlihat pada kode panggilan ketika metode akhirnya keluar.
Ted Hopp
Ini mengatakan apa yang terjadi, tidak mengatakan mengapa (akan sangat berguna jika menunjuk ke tempat ini tercakup dalam JLS).
TJ Crowder
65

Karena nilai balik diletakkan di tumpukan sebelum panggilan ke akhirnya.

Tordek
sumber
3
Itu benar, tetapi tidak menjawab pertanyaan OP tentang mengapa string yang dikembalikan tidak berubah. Itu ada hubungannya dengan string immutability dan referensi versus objek lebih dari mendorong nilai kembali pada stack.
templatetypedef
Kedua masalah tersebut saling terkait.
Tordek
2
@ tempempetetypedef bahkan jika String bisa berubah, menggunakan =tidak akan bermutasi.
Owen
1
@ Saul - Poin Owen (yang benar) adalah bahwa ketidakberdayaan tidak ada hubungannya dengan mengapa finallyblok OP tidak mempengaruhi nilai kembali. Saya pikir apa yang templatetypedef mungkin dapatkan (meskipun ini tidak jelas) adalah bahwa karena nilai yang dikembalikan adalah referensi ke objek yang tidak dapat diubah, bahkan mengubah kode di finallyblok (selain menggunakan returnpernyataan lain ) tidak dapat mempengaruhi nilai yang dikembalikan dari metode.
Ted Hopp
1
@templatetypedef Tidak, tidak. Tidak ada hubungannya dengan String immutability apapun. Hal yang sama akan terjadi dengan tipe lainnya. Ini ada hubungannya dengan mengevaluasi ekspresi-kembali sebelum memasuki blok-akhirnya, dengan kata lain exacfly 'mendorong nilai kembali pada tumpukan'.
Marquis of Lorne
32

Jika kita melihat ke dalam bytecode, kita akan melihat bahwa JDK telah membuat optimasi yang signifikan, dan metode foo () terlihat seperti:

String tmp = null;
try {
    s = "dev"
    tmp = s;
    s = "override variable s";
    return tmp;
} catch (RuntimeException e){
    s = "override variable s";
    throw e;
}

Dan bytecode:

0:  ldc #7;         //loading String "dev"
2:  putstatic   #8; //storing it to a static variable
5:  getstatic   #8; //loading "dev" from a static variable
8:  astore_0        //storing "dev" to a temp variable
9:  ldc #9;         //loading String "override variable s"
11: putstatic   #8; //setting a static variable
14: aload_0         //loading a temp avariable
15: areturn         //returning it
16: astore_1
17: ldc #9;         //loading String "override variable s"
19: putstatic   #8; //setting a static variable
22: aload_1
23: athrow

java mempertahankan string "dev" agar tidak diubah sebelum kembali. Bahkan di sini akhirnya tidak ada blok sama sekali.

Mikhail
sumber
Ini bukan optimasi. Ini hanyalah implementasi dari semantik yang diperlukan.
Marquis of Lorne
22

Ada 2 hal yang patut diperhatikan di sini:

  • String tidak berubah. Saat Anda mengatur s ke "override variable s", Anda mengatur s untuk merujuk ke String yang digarisbawahi, tidak mengubah buffer char yang melekat pada objek s untuk mengubah ke "override variable s".
  • Anda menaruh referensi ke s pada tumpukan untuk kembali ke kode panggilan. Setelah itu (ketika blok akhirnya berjalan), mengubah referensi seharusnya tidak melakukan apa pun untuk nilai pengembalian yang sudah ada di stack.
0xCAFEBABE
sumber
1
jadi jika saya mengambil stringbuffer daripada sengatan akan ditimpa ??
Devendra
10
@ ev - Jika Anda mengubah konten buffer dalam finallyklausa, itu akan terlihat dalam kode panggilan. Namun, jika Anda menetapkan stringbuffer baru s, maka perilakunya akan sama seperti sekarang.
Ted Hopp
ya jika saya tambahkan dalam buffer string pada akhirnya perubahan blok mencerminkan pada output.
Devendra
@ 0xCAFEBABE Anda juga memberikan jawaban dan konsep yang sangat baik, saya sangat berterima kasih kepada Anda.
Devendra
13

Saya sedikit mengubah kode Anda untuk membuktikan maksud Ted.

Seperti yang Anda lihat di output smemang berubah tetapi setelah pengembalian.

public class Test {

public String s;

public String foo() {

    try {
        s = "dev";
        return s;
    } finally {
        s = "override variable s";
        System.out.println("Entry in finally Block");

    }
}

public static void main(String[] xyz) {
    Test obj = new Test();
    System.out.println(obj.foo());
    System.out.println(obj.s);
}
}

Keluaran:

Entry in finally Block 
dev 
override variable s
jujur
sumber
mengapa string yang diganti tidak kembali.
Devendra
2
Seperti Ted dan Tordek sudah mengatakan "nilai pengembalian diletakkan di tumpukan sebelum akhirnya dieksekusi"
Frank
1
Walaupun ini adalah informasi tambahan yang bagus, saya enggan untuk meningkatkannya, karena tidak (sendiri) menjawab pertanyaan.
Joachim Sauer
5

Secara teknis, returnblok uji coba tidak akan diabaikan jika finallyblok ditentukan, hanya jika blok akhirnya juga menyertakan a return.

Ini adalah keputusan desain yang meragukan yang mungkin merupakan kesalahan dalam retrospeksi (seperti referensi yang dapat dibatalkan / dapat diubah secara default, dan, menurut beberapa, mengecek pengecualian). Dalam banyak hal perilaku ini persis konsisten dengan pemahaman sehari-hari tentang apa finallyartinya - "tidak peduli apa yang terjadi sebelumnya di tryblok, selalu jalankan kode ini." Karenanya, jika Anda mengembalikan true dari finallyblok, efek keseluruhan harus selalu ke return s, bukan?

Secara umum, ini jarang idiom yang baik, dan Anda harus menggunakan finallyblok secara bebas untuk membersihkan / menutup sumber daya tetapi jarang jika pernah mengembalikan nilai dari mereka.

Kiran Jujare
sumber
Meragukan mengapa? Ini sesuai dengan urutan evaluasi dalam semua konteks lainnya.
Marquis of Lorne
0

Coba ini: Jika Anda ingin mencetak nilai ganti s.

finally {
    s = "override variable s";    
    System.out.println("Entry in finally Block");
    return s;
}
Achintya Jha
sumber
1
itu memberi peringatan .. akhirnya blok tidak lengkap secara normal
Devendra