Mengapa variabel lokal tidak diinisialisasi di Java?

103

Apakah ada alasan mengapa desainer Java merasa bahwa variabel lokal tidak boleh diberi nilai default? Serius, jika variabel instan dapat diberi nilai default, lalu mengapa kita tidak bisa melakukan hal yang sama untuk variabel lokal?

Dan itu juga menyebabkan masalah seperti yang dijelaskan dalam komentar ini ke posting blog :

Aturan ini paling membuat frustrasi ketika mencoba menutup sumber daya di blok terakhir. Jika saya memberi contoh sumber daya dalam percobaan, tetapi mencoba menutupnya pada akhirnya, saya mendapatkan kesalahan ini. Jika saya memindahkan contoh di luar percobaan, saya mendapatkan kesalahan lain yang menyatakan bahwa itu harus dalam percobaan.

Sangat membuat frustasi.

Shivasubramanian A
sumber
1
Maaf tentang itu ... pertanyaan ini tidak muncul saat saya mengetik pertanyaan .. namun, saya rasa ada perbedaan antara kedua pertanyaan tersebut ... Saya ingin tahu mengapa desainer Java mendesainnya seperti ini, sedangkan pertanyaan yang Anda tunjuk tidak menanyakan bahwa ...
Shivasubramanian A
Lihat juga pertanyaan C # terkait ini: stackoverflow.com/questions/1542824/…
Raedwald
Cukup - karena mudah bagi compiler untuk melacak variabel lokal yang tidak diinisialisasi. Jika bisa sama dengan variabel lain, maka bisa. Kompiler hanya mencoba membantu Anda.
rustyx

Jawaban:

62

Variabel lokal dideklarasikan sebagian besar untuk melakukan beberapa perhitungan. Jadi itu keputusan programmer untuk menetapkan nilai variabel dan tidak boleh mengambil nilai default. Jika programmer, secara tidak sengaja, tidak menginisialisasi variabel lokal dan mengambil nilai default, maka outputnya bisa berupa nilai yang tidak terduga. Jadi dalam kasus variabel lokal, kompilator akan meminta programmer untuk menginisialisasi dengan beberapa nilai sebelum mereka mengakses variabel untuk menghindari penggunaan nilai yang tidak ditentukan.

pejuang
sumber
23

"Masalah" yang Anda tautkan tampaknya menggambarkan situasi ini:

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Keluhan pemberi komentar adalah bahwa kompilator menolak baris di finallybagian tersebut, mengklaim bahwa somungkin tidak diinisialisasi. Komentar tersebut kemudian menyebutkan cara lain untuk menulis kode, mungkin seperti ini:

// Do some work here ...
SomeObject so = new SomeObject();
try {
  so.DoUsefulThings();
} finally {
  so.CleanUp();
}

Pemberi komentar tidak senang dengan solusi itu karena kompilator kemudian mengatakan bahwa kode "harus dalam percobaan." Saya kira itu berarti beberapa kode mungkin menimbulkan pengecualian yang tidak ditangani lagi. Saya tidak yakin. Tidak ada versi kode saya yang menangani pengecualian apa pun, jadi apa pun yang terkait dengan pengecualian di versi pertama harus berfungsi sama di versi kedua.

Bagaimanapun, versi kode kedua ini adalah cara yang benar untuk menulisnya. Di versi pertama, pesan kesalahan penyusun benar. The sovariabel mungkin uninitialized. Secara khusus, jika SomeObjectkonstruktor gagal, sotidak akan diinisialisasi, sehingga akan menjadi kesalahan untuk mencoba memanggil so.CleanUp. Selalu masuk ke trybagian setelah Anda mendapatkan sumber daya yang finallydiselesaikan bagian tersebut.

The try- finallyblok setelah soinisialisasi ada hanya untuk melindungi SomeObjectcontoh, untuk memastikan itu akan dibersihkan tidak peduli apa pun yang terjadi. Jika ada lain hal yang perlu untuk menjalankan, tetapi mereka tidak terkait dengan apakah SomeObjectcontoh itu properti dialokasikan, maka mereka harus pergi di lain try - finallyblok, mungkin salah satu yang membungkus yang saya telah menunjukkan.

Mewajibkan variabel untuk ditetapkan secara manual sebelum digunakan tidak menyebabkan masalah nyata. Ini hanya menyebabkan kerepotan kecil, tetapi kode Anda akan lebih baik untuk itu. Anda akan memiliki variabel dengan cakupan yang lebih terbatas, dan try- finallyblok yang tidak mencoba melindungi terlalu banyak.

Jika variabel lokal memiliki nilai default, maka sopada contoh pertama adalah null. Itu tidak akan benar-benar menyelesaikan apa pun. Alih-alih mendapatkan kesalahan waktu kompilasi di finallyblok, Anda akan NullPointerExceptionbersembunyi di sana yang mungkin menyembunyikan pengecualian lain apa pun yang dapat terjadi di bagian "Lakukan beberapa pekerjaan di sini" pada kode. (Atau apakah pengecualian di finallybagian secara otomatis dikaitkan dengan pengecualian sebelumnya? Saya tidak ingat. Meski begitu, Anda akan memiliki pengecualian tambahan di jalan yang asli.)

Rob Kennedy
sumber
2
mengapa tidak memiliki jika (jadi! = null) ... di blok terakhir?
izb
yang masih akan menyebabkan peringatan / kesalahan kompilator - saya tidak berpikir kompilator mengerti bahwa jika periksa (tetapi saya hanya melakukan ini dari memori, tidak diuji).
Chii
6
Saya akan meletakkan SomeObject so = null sebelum mencoba dan memberi tanda centang pada akhirnya klausa. Dengan cara ini tidak akan ada peringatan compiler.
Juha Syrjälä
Mengapa memperumit masalah? Tulis blok coba-akhirnya dengan cara ini, dan Anda TAHU variabel tersebut memiliki nilai yang valid. Tidak perlu pemeriksaan null.
Rob Kennedy
1
Rob, contoh Anda "new SomeObject ()" sederhana dan tidak ada pengecualian yang harus dibuat di sana, tetapi jika panggilan dapat menghasilkan pengecualian maka akan lebih baik jika itu terjadi di dalam blok percobaan sehingga dapat ditangani.
Sarel Botha
12

Selain itu, dalam contoh di bawah ini, pengecualian mungkin telah dilemparkan ke dalam konstruksi SomeObject, dalam hal ini variabel 'so' akan menjadi null dan panggilan ke CleanUp akan memunculkan NullPointerException

SomeObject so;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  so.CleanUp(); // Compiler error here
}

Yang cenderung saya lakukan adalah ini:

SomeObject so = null;
try {
  // Do some work here ...
  so = new SomeObject();
  so.DoUsefulThings();
} finally {
  if (so != null) {
     so.CleanUp(); // safe
  }
}
Biksu Listrik
sumber
12
Apa yang ingin Anda lakukan?
Electric Monk
2
Yup, jelek. Ya, itu yang saya lakukan juga.
SMBiggs
@ElectricMonk Bentuk mana yang menurut Anda lebih baik, yang Anda tunjukkan atau yang ditampilkan di sini dalam metode getContents (..): javapractices.com/topic/TopicAction.do?Id=126
Atom
11

Perhatikan bahwa variabel instance / anggota terakhir tidak diinisialisasi secara default. Karena itu sudah final dan tidak bisa diubah dalam program setelahnya. Itulah alasan mengapa Java tidak memberikan nilai default apa pun untuk mereka dan memaksa pemrogram untuk menginisialisasi.

Di sisi lain, variabel anggota non-final dapat diubah nanti. Oleh karena itu, kompilator tidak membiarkannya tetap tidak dijalankan, tepatnya, karena itu dapat diubah nanti. Mengenai variabel lokal, cakupan variabel lokal jauh lebih sempit. Compiler tahu kapan akan digunakan. Oleh karena itu, memaksa programmer untuk menginisialisasi variabel masuk akal.

Adeel Ansari
sumber
9

Jawaban sebenarnya untuk pertanyaan Anda adalah karena variabel metode dibuat dengan hanya menambahkan angka ke penunjuk tumpukan. Untuk membidiknya akan menjadi langkah ekstra. Untuk variabel kelas, variabel tersebut dimasukkan ke dalam memori yang diinisialisasi di heap.

Mengapa tidak mengambil langkah ekstra? Ambil langkah mundur - Tidak ada yang menyebutkan bahwa "peringatan" dalam kasus ini adalah Hal yang Sangat Baik.

Anda tidak boleh menginisialisasi variabel Anda ke nol atau nol pada lintasan pertama (saat Anda pertama kali mengkodekannya). Baik tetapkan ke nilai sebenarnya atau jangan tetapkan sama sekali karena jika tidak, maka java dapat memberi tahu Anda saat Anda benar-benar mengacau. Ambil jawaban Electric Monk sebagai contoh yang bagus. Dalam kasus pertama, sebenarnya sangat berguna bahwa ini memberi tahu Anda bahwa jika try () gagal karena konstruktor SomeObject melemparkan pengecualian, maka Anda akan berakhir dengan NPE pada akhirnya. Jika konstruktor tidak dapat mengeluarkan pengecualian, seharusnya tidak di coba.

Peringatan ini adalah pemeriksa programmer multi-jalur yang buruk yang telah menyelamatkan saya dari melakukan hal-hal bodoh karena memeriksa setiap jalur dan memastikan bahwa jika Anda menggunakan variabel di beberapa jalur maka Anda harus menginisialisasi di setiap jalur yang mengarah ke sana . Saya sekarang tidak pernah secara eksplisit menginisialisasi variabel sampai saya menentukan bahwa itu adalah hal yang benar untuk dilakukan.

Selain itu, bukankah lebih baik untuk secara eksplisit mengatakan "int size = 0" daripada "int size" dan membuat programmer berikutnya mengetahui bahwa Anda berniat untuk menjadi nol?

Di sisi lain, saya tidak dapat menemukan satu pun alasan yang valid agar kompiler menginisialisasi semua variabel yang tidak diinisialisasi menjadi 0.

Bill K
sumber
1
Ya, dan ada orang lain yang kurang lebih harus diinisialisasi ke nol karena cara kode mengalir - saya seharusnya tidak mengatakan "tidak pernah" memperbarui jawaban untuk mencerminkan hal ini.
Bill K
4

Saya pikir tujuan utamanya adalah untuk mempertahankan kesamaan dengan C / C ++. Namun kompilator mendeteksi dan memperingatkan Anda tentang penggunaan variabel yang tidak diinisialisasi yang akan mengurangi masalah ke titik minimal. Dari perspektif kinerja, ini sedikit lebih cepat untuk membiarkan Anda mendeklarasikan variabel yang tidak diinisialisasi karena compiler tidak perlu menulis pernyataan penugasan, bahkan jika Anda menimpa nilai variabel di pernyataan berikutnya.

Mehrdad Afshari
sumber
1
Bisa dibilang, kompilator dapat menentukan apakah Anda selalu menetapkan ke variabel sebelum melakukan apa pun dengannya, dan menekan penetapan nilai default otomatis dalam kasus seperti itu. Jika compiler tidak dapat menentukan apakah suatu tugas dilakukan sebelum akses, itu akan menghasilkan penetapan default.
Greg Hewgill
2
Ya, tetapi orang mungkin berpendapat bahwa itu memungkinkan programmer mengetahui apakah dia telah membiarkan variabel tidak diinisialisasi karena kesalahan.
Mehrdad Afshari
1
Kompilator juga dapat melakukannya dalam kedua kasus tersebut. :) Secara pribadi, saya lebih suka kompilator memperlakukan variabel yang tidak diinisialisasi sebagai kesalahan. Artinya saya mungkin telah melakukan kesalahan di suatu tempat.
Greg Hewgill
Saya bukan orang Java, tapi saya suka cara C # menanganinya. Perbedaannya adalah dalam kasus itu, kompilator harus mengeluarkan peringatan, yang mungkin membuat Anda mendapatkan beberapa ratus peringatan untuk program Anda yang benar;)
Mehrdad Afshari
Apakah itu memperingatkan untuk variabel anggota juga?
Adeel Ansari
4

(Mungkin terlihat aneh untuk memposting jawaban baru begitu lama setelah pertanyaan, tetapi muncul duplikat .)

Bagi saya, alasannya begini: Tujuan variabel lokal berbeda dari tujuan variabel instan. Variabel lokal ada untuk digunakan sebagai bagian dari perhitungan; Variabel instance ada di sana untuk memuat status. Jika Anda menggunakan variabel lokal tanpa memberinya nilai, itu hampir pasti merupakan kesalahan logika.

Yang mengatakan, saya benar-benar bisa ketinggalan mengharuskan variabel instan selalu secara eksplisit diinisialisasi; kesalahan akan terjadi pada setiap konstruktor di mana hasilnya memungkinkan variabel contoh yang tidak diinisialisasi (misalnya, tidak diinisialisasi pada deklarasi dan tidak dalam konstruktor). Tapi bukan itu keputusan Gosling, dkk. al., diambil di awal 90-an, jadi di sinilah kita. (Dan saya tidak mengatakan mereka melakukan panggilan yang salah.)

Saya tidak bisa ketinggalan variabel lokal default. Ya, kita seharusnya tidak bergantung pada kompiler untuk memeriksa ulang logika kita, dan yang satu tidak, tetapi masih berguna ketika kompilator menangkapnya. :-)

TJ Crowder
sumber
"Yang mengatakan, saya benar-benar bisa ketinggalan mengharuskan variabel instan selalu secara eksplisit diinisialisasi ..." yang, FWIW, adalah arah yang mereka ambil di TypeScript.
TJ Crowder
3

Lebih efisien untuk tidak menginisialisasi variabel, dan dalam kasus variabel lokal lebih aman untuk melakukannya, karena inisialisasi dapat dilacak oleh kompilator.

Dalam kasus di mana Anda membutuhkan variabel untuk diinisialisasi, Anda selalu dapat melakukannya sendiri, jadi ini bukan masalah.

starblue
sumber
3

Ide di balik variabel lokal adalah variabel tersebut hanya ada di dalam ruang lingkup terbatas yang dibutuhkannya. Dengan demikian, harus ada sedikit alasan untuk ketidakpastian mengenai nilai, atau setidaknya, dari mana nilai itu berasal. Saya bisa membayangkan banyak kesalahan yang timbul karena memiliki nilai default untuk variabel lokal.

Misalnya, pertimbangkan kode sederhana berikut ... ( NB mari kita asumsikan untuk tujuan demonstrasi bahwa variabel lokal diberi nilai default, seperti yang ditentukan, jika tidak diinisialisasi secara eksplisit )

System.out.println("Enter grade");
int grade = new Scanner(System.in).nextInt(); //I won't bother with exception handling here, to cut down on lines.
char letterGrade; //let us assume the default value for a char is '\0'
if (grade >= 90)
    letterGrade = 'A';
else if (grade >= 80)
    letterGrade = 'B';
else if (grade >= 70)
    letterGrade = 'C';
else if (grade >= 60)
    letterGrade = 'D';
else
    letterGrade = 'F';
System.out.println("Your grade is " + letterGrade);

Ketika semua dikatakan dan dilakukan, dengan asumsi kompilator menetapkan nilai default '\ 0' ke letterGrade , kode ini seperti yang tertulis akan berfungsi dengan baik. Namun, bagaimana jika kita lupa pernyataan lain?

Uji coba kode kami mungkin menghasilkan hal berikut

Enter grade
43
Your grade is

Hasil ini, meskipun sudah diharapkan, pasti bukan maksud pembuat kode. Memang, mungkin dalam sebagian besar kasus (atau setidaknya, jumlah yang signifikan, daripadanya), nilai default bukanlah nilai yang diinginkan , jadi dalam sebagian besar kasus, nilai default akan menghasilkan kesalahan. Lebih masuk akal untuk memaksa pembuat kode untuk menetapkan nilai awal ke variabel lokal sebelum menggunakannya, karena kesedihan debugging yang disebabkan oleh lupa = 1dalam for(int i = 1; i < 10; i++)jauh lebih besar daripada kenyamanan karena tidak harus menyertakan = 0in for(int i; i < 10; i++).

Memang benar bahwa blok coba-tangkap-akhirnya bisa menjadi sedikit berantakan (tetapi sebenarnya ini bukan tangkapan-22 seperti yang tampaknya disarankan oleh kutipan), ketika misalnya sebuah objek melempar pengecualian yang dicentang dalam konstruktornya, namun untuk satu alasan atau lainnya, sesuatu harus dilakukan untuk objek ini pada akhirnya blok pada akhirnya. Contoh sempurna dari ini adalah ketika berhadapan dengan sumber daya, yang harus ditutup.

Salah satu cara untuk menangani ini di masa lalu mungkin seperti itu ...

Scanner s = null; //declared and initialized to null outside the block. This gives us the needed scope, and an initial value.
try {
    s = new Scanner(new FileInputStream(new File("filename.txt")));
    int someInt = s.nextInt();
} catch (InputMismatchException e) {
    System.out.println("Some error message");
} catch (IOException e) {
    System.out.println("different error message"); 
} finally {
    if (s != null) //in case exception during initialization prevents assignment of new non-null value to s.
        s.close();
}

Namun, pada Java 7, blok terakhir ini tidak lagi diperlukan menggunakan try-with-resources, seperti itu.

try (Scanner s = new Scanner(new FileInputStream(new File("filename.txt")))) {
...
...
} catch(IOException e) {
    System.out.println("different error message");
}

Yang mengatakan, (seperti namanya) ini hanya berfungsi dengan sumber daya.

Dan sementara contoh pertama agak menjijikkan, ini mungkin lebih mengacu pada cara coba-tangkap-akhirnya atau kelas-kelas ini diimplementasikan daripada berbicara tentang variabel lokal dan bagaimana mereka diimplementasikan.

Memang benar bahwa bidang diinisialisasi ke nilai default, tetapi ini sedikit berbeda. Saat Anda mengatakan, misalnya, int[] arr = new int[10];segera setelah Anda menginisialisasi larik ini, objek tersebut ada dalam memori di lokasi tertentu. Mari kita asumsikan sejenak bahwa tidak ada nilai default, tetapi nilai awalnya adalah deretan 1 dan 0 apa pun yang kebetulan ada di lokasi memori tersebut saat ini. Hal ini dapat menyebabkan perilaku non-deterministik dalam beberapa kasus.

Misalkan kita memiliki ...

int[] arr = new int[10];
if(arr[0] == 0)
    System.out.println("Same.");
else
    System.out.println("Not same.");

Sangat mungkin bahwa Same.mungkin ditampilkan dalam satu proses dan Not same.mungkin ditampilkan di proses lain. Masalahnya bisa menjadi lebih menyedihkan setelah Anda mulai berbicara tentang variabel referensi.

String[] s = new String[5];

Menurut definisi, setiap elemen s harus mengarah ke String (atau null). Namun, jika nilai awalnya adalah rangkaian 0 dan 1 apa pun yang terjadi di lokasi memori ini, tidak hanya tidak ada jaminan Anda akan mendapatkan hasil yang sama setiap saat, tetapi juga tidak ada jaminan bahwa objek s [0] menunjuk to (dengan asumsi itu menunjuk ke sesuatu yang berarti) bahkan adalah String (mungkin itu Kelinci,: p )! Kurangnya perhatian terhadap tipe ini akan berhadapan dengan hampir semua yang membuat Java Java. Jadi meskipun memiliki nilai default untuk variabel lokal dapat dilihat sebagai pilihan terbaik, memiliki nilai default untuk variabel instan lebih mendekati kebutuhan .

kumquatfelafel
sumber
1

jika saya tidak salah, alasan lain bisa jadi

Pemberian nilai default variabel anggota merupakan bagian dari pemuatan kelas

Pemuatan kelas adalah hal yang berjalan dalam java artinya ketika Anda membuat objek maka kelas tersebut dimuat dengan kelas yang memuat hanya variabel anggota yang diinisialisasi dengan nilai default JVM tidak membutuhkan waktu untuk memberikan nilai default ke variabel lokal Anda karena beberapa metode tidak akan pernah dipanggil karena pemanggilan metode bisa bersyarat jadi mengapa butuh waktu untuk memberi mereka nilai default dan mengurangi kinerja jika default tersebut tidak akan pernah digunakan.

Abhinav Chauhan
sumber
0

Eclipse bahkan memberi Anda peringatan tentang variabel yang tidak diinisialisasi, jadi itu menjadi cukup jelas. Secara pribadi saya pikir itu adalah hal yang baik bahwa ini adalah perilaku default, jika tidak, aplikasi Anda mungkin menggunakan nilai yang tidak terduga, dan alih-alih kompiler yang membuat kesalahan, ia tidak akan melakukan apa pun (tetapi mungkin memberi peringatan) dan kemudian Anda akan menggaruk kepala Anda tentang mengapa hal-hal tertentu tidak berperilaku sebagaimana mestinya.

Kezzer
sumber
0

Variabel lokal disimpan di tumpukan, tetapi variabel instan disimpan di heap, jadi ada beberapa kemungkinan bahwa nilai sebelumnya di tumpukan akan dibaca, bukan nilai default seperti yang terjadi di heap. Oleh karena itu, jvm tidak mengizinkan untuk menggunakan variabel lokal tanpa melakukan inisialisasi.

David Santamaria
sumber
2
Benar-benar salah ... semua non-primitif Java disimpan di heap terlepas dari kapan dan bagaimana mereka dibuat
gshauger
Sebelum Java 7, variabel instan disimpan di heap dan variabel lokal ditemukan di stack. Namun, objek apa pun yang direferensikan oleh variabel lokal akan ditemukan di heap. Mulai Java 7, "Java Hotspot Server Compiler" mungkin menjalankan "analisis pelolosan" dan memutuskan untuk mengalokasikan beberapa objek di tumpukan, bukan di heap.
mamills
0

Variabel instance akan memiliki nilai default tetapi variabel lokal tidak boleh memiliki nilai default. Karena variabel lokal pada dasarnya berada dalam metode / perilaku, tujuan utamanya adalah untuk melakukan beberapa operasi atau kalkulasi. Oleh karena itu, bukanlah ide yang baik untuk menetapkan nilai default untuk variabel lokal. Jika tidak, akan sangat sulit dan memakan waktu untuk memeriksa alasan jawaban yang tidak terduga.

Xiaogang
sumber
-1

Jawabannya adalah variabel instan dapat diinisialisasi di konstruktor kelas atau metode kelas apa pun, Tetapi dalam kasus variabel lokal, setelah Anda menentukan apa pun dalam metode yang tetap selamanya di kelas.

Akash5288
sumber
-2

Saya dapat memikirkan 2 alasan berikut

  1. Seperti sebagian besar jawaban yang dikatakan dengan meletakkan batasan dalam menginisialisasi variabel lokal, dapat dipastikan bahwa variabel lokal mendapatkan nilai yang diinginkan pemrogram dan memastikan hasil yang diharapkan dihitung.
  2. Variabel instans dapat disembunyikan dengan mendeklarasikan variabel lokal (nama yang sama) - untuk memastikan perilaku yang diharapkan, variabel lokal dipaksa untuk diberi nilai. (Akan benar-benar menghindari ini)
Mitra
sumber
Bidang tidak dapat diganti. Paling banyak, mereka dapat disembunyikan, dan saya gagal untuk melihat bagaimana menyembunyikan akan mengganggu pemeriksaan untuk inisialisasi?
meriton
Disembunyikan kanan Jika seseorang memutuskan untuk membuat variabel lokal dengan nama yang sama seperti instance, karena kendala ini, variabel lokal akan diinisialisasi dengan nilai yang dipilih secara pupus (selain nilai variabel instance)
Mitra