Saya membaca beberapa teks Java dan mendapatkan kode berikut:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
Dalam teks tersebut penulis tidak memberikan penjelasan yang jelas dan efek dari baris terakhir tersebut adalah: a[1] = 0;
Saya tidak begitu yakin bahwa saya mengerti: bagaimana evaluasi itu terjadi?
java
operator-precedence
ipkiss
sumber
sumber
Jawaban:
Izinkan saya mengatakan ini dengan sangat jelas, karena orang salah paham sepanjang waktu:
Urutan evaluasi subekspresi tidak bergantung pada asosiativitas dan prioritas . Asosiatif dan prioritas menentukan dalam urutan apa operator dieksekusi tetapi tidak menentukan dalam urutan apa subekspresi dievaluasi. Pertanyaan Anda adalah tentang urutan di mana sub - ekspresi dievaluasi.
Pertimbangkan
A() + B() + C() * D()
. Perkalian lebih diutamakan daripada penjumlahan, dan penjumlahan adalah asosiatif kiri, jadi ini setara dengan(A() + B()) + (C() * D())
Tetapi mengetahui bahwa hanya memberi tahu Anda bahwa penjumlahan pertama akan terjadi sebelum penjumlahan kedua, dan perkalian akan terjadi sebelum penjumlahan kedua. Itu tidak memberi tahu Anda dalam urutan apa A (), B (), C () dan D () akan dipanggil! (Ini juga tidak memberi tahu Anda apakah perkalian terjadi sebelum atau setelah penjumlahan pertama.) Sangat mungkin untuk mematuhi aturan presedensi dan asosiativitas dengan menyusun ini sebagai:d = D() // these four computations can happen in any order b = B() c = C() a = A() sum = a + b // these two computations can happen in any order product = c * d result = sum + product // this has to happen last
Semua aturan prioritas dan asosiatif diikuti di sana - penjumlahan pertama terjadi sebelum penjumlahan kedua, dan perkalian terjadi sebelum penjumlahan kedua. Jelas kita dapat melakukan panggilan ke A (), B (), C () dan D () dalam urutan apa pun dan tetap mematuhi aturan prioritas dan asosiatif!
Kami memerlukan aturan yang tidak terkait dengan aturan prioritas dan asosiatif untuk menjelaskan urutan subekspresi dievaluasi. Aturan yang relevan di Java (dan C #) adalah "subekspresi dievaluasi dari kiri ke kanan". Karena A () muncul di kiri C (), A () dievaluasi terlebih dahulu, terlepas dari fakta bahwa C () terlibat dalam perkalian dan A () hanya terlibat dalam penjumlahan.
Jadi sekarang Anda memiliki cukup informasi untuk menjawab pertanyaan Anda. Dalam
a[b] = b = 0
aturan asosiatif mengatakan bahwa inia[b] = (b = 0);
tetapi itu tidak berarti bahwa yangb=0
berjalan lebih dulu! Aturan prioritas mengatakan bahwa pengindeksan lebih diutamakan daripada tugas, tetapi itu tidak berarti bahwa pengindeks berjalan sebelum tugas paling kanan .(PEMBARUAN: Versi sebelumnya dari jawaban ini memiliki beberapa kelalaian kecil dan praktis tidak penting di bagian berikutnya yang telah saya koreksi. Saya juga menulis artikel blog yang menjelaskan mengapa aturan ini masuk akal di Java dan C # di sini: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
Presedensi dan asosiatif hanya memberi tahu kita bahwa penetapan nol ke
b
harus terjadi sebelum penetapan kea[b]
, karena penetapan nol menghitung nilai yang ditetapkan dalam operasi pengindeksan. Precedence dan associativity saja katakanlah apa-apa tentang apakaha[b]
dievaluasi sebelum atau setelah itub=0
.Sekali lagi, ini sama seperti:
A()[B()] = C()
- Yang kita tahu adalah bahwa pengindeksan harus dilakukan sebelum penugasan. Kami tidak tahu apakah A (), B (), atau C () berjalan lebih dulu berdasarkan prioritas dan asosiatif . Kami membutuhkan aturan lain untuk memberi tahu kami hal itu.Aturannya adalah, sekali lagi, "ketika Anda memiliki pilihan tentang apa yang harus dilakukan pertama kali, selalu belok kiri ke kanan". Namun, ada kerutan yang menarik dalam skenario khusus ini. Apakah efek samping dari pengecualian yang dilempar disebabkan oleh koleksi null atau indeks di luar rentang dianggap sebagai bagian dari penghitungan sisi kiri tugas, atau bagian dari penghitungan tugas itu sendiri? Java memilih yang terakhir. (Tentu saja, ini adalah perbedaan yang hanya penting jika kodenya sudah salah , karena kode yang benar tidak mengurangi nol atau meneruskan indeks yang buruk sejak awal.)
Lalu apa yang terjadi?
a[b]
adalah di sebelah kirib=0
, jadia[b]
run pertama , menghasilkana[1]
. Namun, pemeriksaan validitas operasi pengindeksan ini tertunda.b=0
terjadilah.a
valid dana[1]
dalam jangkauan terjadia[1]
terjadi terakhir.Jadi, meskipun dalam kasus khusus ini ada beberapa kehalusan yang perlu dipertimbangkan untuk kasus kesalahan langka yang semestinya tidak terjadi dalam kode yang benar sejak awal, secara umum Anda dapat bernalar: hal-hal di kiri terjadi sebelum hal-hal di kanan . Itulah aturan yang Anda cari. Pembicaraan tentang prioritas dan asosiatif sama-sama membingungkan dan tidak relevan.
Orang-orang melakukan kesalahan ini sepanjang waktu , bahkan orang yang seharusnya lebih tahu. Saya telah menyunting terlalu banyak buku pemrograman yang menyatakan aturannya secara tidak benar, sehingga tidak mengherankan jika banyak orang memiliki keyakinan yang salah sepenuhnya tentang hubungan antara presedensi / asosiativitas, dan urutan evaluasi - yaitu, bahwa pada kenyataannya tidak ada hubungan seperti itu. ; mereka mandiri.
Jika topik ini menarik minat Anda, lihat artikel saya tentang subjek untuk bacaan lebih lanjut:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Ini tentang C #, tetapi sebagian besar dari hal ini berlaku sama baiknya untuk Java.
sumber
Meskipun demikian, jawaban ahli Eric Lippert tidak terlalu membantu karena berbicara tentang bahasa yang berbeda. Ini adalah Java, di mana Spesifikasi Bahasa Java adalah deskripsi definitif dari semantik. Secara khusus, §15.26.1 relevan karena menjelaskan urutan evaluasi untuk
=
operator (kita semua tahu bahwa itu asosiatif-kanan, ya?). Memotongnya sedikit ke bagian yang kami pedulikan dalam pertanyaan ini:[… Kemudian menjelaskan arti sebenarnya dari tugas itu sendiri, yang dapat kita abaikan di sini untuk singkatnya…]
Singkatnya, Java memiliki urutan evaluasi yang didefinisikan sangat dekat yang cukup banyak persis dari kiri ke kanan dalam argumen untuk setiap operator atau pemanggilan metode. Penugasan array adalah salah satu kasus yang lebih kompleks, tetapi bahkan di sana masih L2R. (JLS menganjurkan agar Anda tidak menulis kode yang memerlukan batasan semantik kompleks semacam ini , dan begitu juga saya: Anda bisa mendapatkan lebih dari cukup masalah hanya dengan satu tugas per pernyataan!)
C dan C ++ jelas berbeda dengan Java di area ini: definisi bahasa mereka membiarkan urutan evaluasi tidak ditentukan secara sengaja untuk memungkinkan lebih banyak pengoptimalan. C # tampaknya seperti Java, tetapi saya tidak terlalu memahami literaturnya untuk dapat menunjukkan definisi formal. (Ini benar-benar bervariasi oleh bahasa meskipun, Ruby secara ketat L2R, seperti Tcl - meskipun tidak memiliki sebuah operator penugasan per se untuk alasan tidak relevan di sini - dan Python adalah L2R tapi R2L dalam hal tugas , yang saya temukan aneh tapi ada Anda pergi .)
sumber
a[-1]=c
,c
dievaluasi, sebelum-1
dianggap tidak valid.a[b] = b = 0;
1) operator pengindeksan array memiliki prioritas lebih tinggi daripada operator penugasan (lihat jawaban ini ):
(a[b]) = b = 0;
2) Menurut 15.26. Operator Penugasan JLS
(a[b]) = (b=0);
3) Menurut 15.7. Urutan Evaluasi JLS
dan
Begitu:
a)
(a[b])
dievaluasi terlebih dahulua[1]
b) kemudian
(b=0)
dievaluasi ke0
c)
(a[1] = 0)
dievaluasi terakhirsumber
Kode Anda sama dengan:
int[] a = {4,4}; int b = 1; c = b; b = 0; a[c] = b;
yang menjelaskan hasilnya.
sumber
Pertimbangkan contoh lain yang lebih mendalam di bawah ini.
Sebagai Aturan Umum:
Sebaiknya sediakan tabel Urutan Aturan dan Asosiasi yang Diutamakan untuk dibaca saat menyelesaikan pertanyaan ini, misalnya http://introcs.cs.princeton.edu/java/11precedence/
Ini contoh yang bagus:
System.out.println(3+100/10*2-13);
Pertanyaan: Apa Output dari Baris di atas?
Jawaban: Terapkan Aturan Prioritas dan Asosiatif
Langkah 1: Menurut aturan prioritas: / dan * operator mengambil prioritas di atas operator + -. Oleh karena itu, titik awal untuk menjalankan persamaan ini akan dipersempit menjadi:
100/10*2
Langkah 2: Menurut aturan dan prioritas: / dan * memiliki prioritas yang sama.
Karena operator / dan * memiliki prioritas yang sama, kita perlu melihat asosiasi antara operator tersebut.
Menurut ATURAN ASOSIATIVITAS dari dua operator tertentu ini, kami mulai mengeksekusi persamaan dari LEFT TO RIGHT yaitu 100/10 dieksekusi terlebih dahulu:
100/10*2 =100/10 =10*2 =20
Langkah 3: Persamaan tersebut sekarang berada dalam status eksekusi berikut:
=3+20-13
Menurut aturan dan prioritas: + dan - memiliki prioritas yang sama.
Sekarang kita perlu melihat asosiasi antara operator + dan - operator. Menurut asosiasi dari dua operator tertentu ini, kami mulai mengeksekusi persamaan dari KIRI ke KANAN yaitu 3 + 20 dieksekusi terlebih dahulu:
=3+20 =23 =23-13 =10
10 adalah keluaran yang benar saat dikompilasi
Sekali lagi, penting untuk memiliki tabel Urutan Aturan Prioritas dan Asosiasi dengan Anda saat menyelesaikan pertanyaan ini, misalnya http://introcs.cs.princeton.edu/java/11precedence/
sumber
10 - 4 - 3
.+
adalah operator unary (yang memiliki asosiasi kanan ke kiri), tetapi aditif + dan - sama seperti perkalian * /% tersisa untuk asosiatif yang benar.