switch
lama yang baik switch
ada di Jawa sejak hari pertama. Kita semua menggunakannya dan terbiasa dengannya - terutama kebiasaannya. (Apakah ada orang lain yang kesal karena break
?) Tetapi sekarang semuanya mulai berubah: di Java 12, sakelar alih-alih operator telah menjadi ekspresi:
boolean result = switch(ternaryBool) { case TRUE -> true; case FALSE -> false; case FILE_NOT_FOUND -> throw new UncheckedIOException( "This is ridiculous!", new FileNotFoundException());
Switch sekarang memiliki kemampuan untuk mengembalikan hasil kerjanya, yang dapat ditugaskan ke variabel; Anda juga dapat menggunakan sintaks gaya lambda, yang memungkinkan Anda untuk menyingkirkan pass-through untuk semua case
di mana tidak ada pernyataan break
.
Dalam panduan ini, saya akan memberi tahu Anda tentang semua yang perlu Anda ketahui tentang beralih ekspresi di Jawa 12.
Pratinjau
Menurut spesifikasi awal bahasa , ekspresi saklar baru mulai diimplementasikan di Jawa 12.
Ini berarti bahwa konstruk kontrol ini dapat diubah dalam versi spesifikasi bahasa yang akan datang.
Untuk mulai menggunakan versi baru switch
Anda perlu menggunakan opsi baris perintah --enable-preview
baik saat kompilasi maupun saat startup program (Anda juga harus menggunakan --release 12
saat mengkompilasi - catatan oleh penerjemah).
Jadi perlu diingat bahwa switch , sebagai ekspresi, saat ini tidak memiliki sintaks akhir di Java 12.
Jika Anda memiliki keinginan untuk bermain dengan semua ini sendiri, maka Anda dapat mengunjungi proyek demo Java X saya di github .
Masalah dengan pernyataan dalam switch
Sebelum kita beralih ke tinjauan umum inovasi, mari kita cepat mengevaluasi satu situasi. Misalkan kita dihadapkan dengan boulean ternary "mengerikan" dan ingin mengubahnya menjadi boulean biasa. Inilah salah satu cara untuk melakukan ini:
boolean result; switch(ternaryBool) { case TRUE: result = true;
Setuju bahwa ini sangat merepotkan. Seperti banyak opsi sakelar lain yang ditemukan di "nature", contoh di atas hanya menghitung nilai variabel dan menetapkannya, tetapi implementasinya dilewati (nyatakan result
pengidentifikasi dan gunakan nanti), diulangi ( break
saya 'dan selalu hasil dari copy-paste) dan rawan kesalahan (lupa cabang lain? Oh!). Jelas ada sesuatu untuk diperbaiki.
Mari kita coba selesaikan masalah ini dengan menempatkan sakelar pada metode terpisah:
private static boolean toBoolean(Bool ternaryBool) { switch(ternaryBool) { case TRUE: return true; case FALSE: return false; case FILE_NOT_FOUND: throw new UncheckedIOException("This is ridiculous!", new FileNotFoundException());
Ini jauh lebih baik: tidak ada variabel dummy, tidak ada break
mengacaukan kode dan pesan penyusun tentang tidak adanya default
(bahkan jika ini tidak perlu, seperti dalam kasus ini).
Tetapi, jika Anda memikirkannya, kami tidak diharuskan membuat metode hanya untuk menghindari fitur bahasa yang canggung. Dan ini bahkan tanpa mempertimbangkan bahwa refactoring seperti itu tidak selalu memungkinkan. Tidak, kami membutuhkan solusi yang lebih baik!
Memperkenalkan ekspresi saklar!
Seperti yang saya tunjukkan di awal artikel, dimulai dengan Java 12 ke atas, Anda dapat memecahkan masalah di atas sebagai berikut:
boolean result = switch(ternaryBool) { case TRUE -> true; case FALSE -> false; case FILE_NOT_FOUND -> throw new UncheckedIOException( "This is ridiculous!", new FileNotFoundException());
Saya pikir ini cukup jelas: jika ternartBool
adalah TRUE
, maka result
'akan disetel ke true
(dengan kata lain, TRUE
berubah menjadi true
). FALSE
menjadi false
.
Dua pikiran segera muncul:
switch
mungkin memiliki hasil;- ada apa dengan panah?
Sebelum mempelajari detail fitur sakelar baru, pada awalnya saya akan berbicara tentang dua aspek utama ini.
Ekspresi atau pernyataan
Anda mungkin terkejut bahwa peralihan sekarang menjadi ekspresi. Tapi apa yang dia lakukan sebelumnya?
Sebelum Java 12, sebuah saklar adalah operator - sebuah konstruksi imperatif yang mengontrol aliran kontrol.
Pikirkan perbedaan antara versi lama dan baru dari saklar sebagai perbedaan antara if
dan operator ternary. Mereka berdua memeriksa kondisi logis dan melakukan percabangan tergantung pada hasilnya.
Perbedaannya adalah bahwa if
hanya mengeksekusi blok yang sesuai, sedangkan operator ternary mengembalikan beberapa hasil:
if(condition) { result = doThis(); } else { result = doThat(); } result = condition ? doThis() : doThat();
Hal yang sama berlaku untuk switch : sebelum Java 12, jika Anda ingin menghitung nilai dan menyimpan hasilnya, Anda akan menugaskannya ke variabel (dan kemudian break
), atau mengembalikannya dari metode yang dibuat khusus untuk switch
.
Sekarang, seluruh ekspresi pernyataan switch dievaluasi (cabang yang sesuai dipilih untuk dieksekusi), dan hasil perhitungan dapat ditugaskan ke variabel.
Perbedaan lain antara ekspresi dan pernyataan adalah bahwa pernyataan switch , karena merupakan bagian dari pernyataan, harus diakhiri dengan tanda titik koma, tidak seperti pernyataan switch klasik.
Panah atau titik dua
Contoh pengantar menggunakan sintaks gaya lambda baru dengan panah antara label dan bagian yang berjalan. Penting untuk dipahami bahwa untuk ini, tidak perlu menggunakan switch
sebagai ekspresi. Faktanya, contoh di bawah ini setara dengan kode yang diberikan di awal artikel:
boolean result = switch(ternaryBool) { case TRUE: break true; case FALSE: break false; case FILE_NOT_FOUND: throw new UncheckedIOException( "This is ridiculous!", new FileNotFoundException()); default: throw new IllegalArgumentException("Seriously?!!?"); };
Perhatikan bahwa sekarang Anda dapat menggunakan break
dengan suatu nilai! Ini sangat cocok dengan switch
gaya lama yang menggunakan break
tanpa makna. Jadi dalam hal apa panah berarti ekspresi daripada operator, mengapa ada di sini? Hanya sintaks hipster?
Secara historis, tanda titik dua hanya menandai titik masuk ke blok pernyataan. Dari titik ini, eksekusi semua kode di bawah ini dimulai, bahkan ketika label lain ditemukan. Sebagai switch
kami mengetahui hal ini sebagai melintas ke case
berikutnya (fall-through): label case
menentukan ke mana aliran kontrol melompat. Untuk melengkapinya, Anda perlu break
atau return
.
Pada gilirannya, menggunakan panah berarti hanya blok di sebelah kanannya yang akan dieksekusi. Dan tidak ada "gagal."
Lebih lanjut tentang evolusi saklar
Banyak tag pada kasing
Sejauh ini, setiap case
hanya case
satu label. Tetapi sekarang semuanya telah berubah - satu case
dapat sesuai dengan beberapa label:
String result = switch(ternaryBool) { case TRUE, FALSE -> "sane";
Perilaku harus jelas: TRUE
dan FALSE
menghasilkan hasil yang sama - ungkapan "waras" dievaluasi.
Ini adalah inovasi yang lumayan bagus yang menggantikan banyak penggunaan case
ketika diperlukan untuk mengimplementasikan transisi pass-through ke case
berikutnya.
Jenis Di Luar Enum
Semua switch
contoh dalam artikel ini menggunakan enum
. Bagaimana dengan tipe lain? Pernyataan dan switch
juga dapat berfungsi dengan String
, int
, (periksa dokumentasi ) short
, byte
, char
, dan pembungkusnya. Sejauh ini tidak ada yang berubah di sini, meskipun gagasan untuk menggunakan tipe data seperti float
dan long
masih valid (dari paragraf kedua hingga paragraf terakhir).
Lebih lanjut tentang panah
Mari kita lihat dua properti khusus untuk bentuk panah dari catatan pemisah:
- kurangnya transisi ujung ke ujung ke
case
selanjutnya; - blok operator.
Tidak ada pass-through ke case selanjutnya
Inilah yang dikatakan JEP 325 tentang ini:
Desain saat ini dari switch
di Jawa terkait erat dengan bahasa seperti C dan C ++ dan mendukung semantik end-to-end secara default. Meskipun cara tradisional pengendalian ini sering berguna untuk menulis kode tingkat rendah (seperti parser untuk pengkodean biner), karena switch
digunakan dalam kode tingkat yang lebih tinggi, kesalahan pendekatan ini mulai lebih besar daripada fleksibilitasnya.
Saya sepenuhnya setuju dan menyambut kesempatan untuk menggunakan sakelar tanpa perilaku default:
switch(ternaryBool) { case TRUE, FALSE -> System.out.println("Bool was sane");
Penting untuk mengetahui bahwa ini tidak ada hubungannya dengan apakah Anda menggunakan sakelar sebagai ekspresi atau pernyataan. Faktor penentu di sini adalah panah terhadap usus besar.
Blok Operator
Seperti dalam kasus lambdas, panah dapat menunjuk ke salah satu operator (seperti di atas) atau blok yang disorot dengan kurung kurawal:
boolean result = switch(Bool.random()) { case TRUE -> { System.out.println("Bool true");
Blok yang harus dibuat untuk operator multi-line memiliki keuntungan tambahan (yang tidak diperlukan saat menggunakan titik dua), yang berarti bahwa untuk menggunakan nama variabel yang sama di cabang yang berbeda, switch
tidak memerlukan pemrosesan khusus.
Jika Anda merasa tidak biasa keluar dari blok menggunakan break
daripada return
, maka jangan khawatir - ini juga membingungkan saya dan tampak aneh. Tapi kemudian saya memikirkannya dan sampai pada kesimpulan bahwa itu masuk akal, karena ia mempertahankan gaya lama dari switch
, yang menggunakan break
tanpa nilai.
Pelajari lebih lanjut tentang pergantian pernyataan
Dan last but not least, spesifikasi menggunakan switch
sebagai ekspresi:
- banyak ekspresi;
- pengembalian awal (
return
awal); - cakupan semua nilai.
Harap perhatikan bahwa tidak masalah bentuk apa yang digunakan!
Beragam ekspresi
Beralih ekspresi adalah beberapa ekspresi. Ini berarti bahwa mereka tidak memiliki tipe mereka sendiri, tetapi dapat menjadi salah satu dari beberapa tipe. Paling sering, ekspresi lambda digunakan sebagai ekspresi seperti: s -> s + " "
, mungkin Function<String, String>
, tetapi mungkin juga Function<Serializable, Object>
atau UnaryOperator<String>
.
Menggunakan ekspresi saklar, suatu tipe ditentukan oleh interaksi antara di mana saklar digunakan dan tipe cabangnya. Jika ekspresi sakelar ditetapkan ke variabel yang diketik, diteruskan sebagai argumen, atau digunakan dalam konteks di mana tipe persisnya diketahui (ini disebut tipe target), maka semua cabangnya harus cocok dengan tipe itu. Inilah yang telah kami lakukan sejauh ini:
String result = switch (ternaryBool) { case TRUE, FALSE -> "sane"; default -> "insane"; };
Sebagai hasilnya, switch
ditugaskan ke variabel result
dari tipe String
. Oleh karena itu, String
adalah tipe target, dan semua cabang harus mengembalikan hasil dari tipe String
.
Hal yang sama terjadi di sini:
Serializable serializableMessage = switch (bool) { case TRUE, FALSE -> "sane";
Apa yang akan terjadi sekarang?
(Untuk penggunaan jenis var, baca di artikel terakhir kami 26 rekomendasi untuk menggunakan jenis var di Jawa - catatan oleh penerjemah)
Jika tipe target tidak diketahui, karena fakta bahwa kami menggunakan var, tipe tersebut dihitung dengan menemukan supertipe paling spesifik dari tipe yang dibuat oleh cabang.
Kembali lebih awal
Konsekuensi dari perbedaan antara ekspresi dan switch
adalah bahwa Anda dapat menggunakan return
untuk keluar dari switch
:
public String sanity(Bool ternaryBool) { switch (ternaryBool) {
... Anda tidak dapat menggunakan return
di dalam ekspresi ...
public String sanity(Bool ternaryBool) { String result = switch (ternaryBool) {
Ini masuk akal apakah Anda menggunakan panah atau titik dua.
Mencakup semua opsi
Jika Anda menggunakan switch
sebagai operator, maka tidak masalah jika semua opsi dicakup atau tidak. Tentu saja, Anda dapat melewatkan case
secara tidak sengaja, dan kode tidak akan berfungsi dengan benar, tetapi kompiler tidak peduli - Anda, IDE Anda dan alat analisis kode Anda akan dibiarkan begitu saja.
Berpindah ekspresi memperparah masalah ini. Di mana harus beralih pergi jika label yang diinginkan hilang? Satu-satunya jawaban yang dapat diberikan Java adalah mengembalikan null
untuk tipe referensi dan nilai default untuk primitif. Ini akan menyebabkan banyak kesalahan dalam kode utama.
Untuk mencegah hasil seperti itu, kompiler dapat membantu Anda. Untuk pernyataan pergantian, kompiler akan bersikeras bahwa semua opsi yang mungkin tercakup. Mari kita lihat contoh yang mungkin menyebabkan kesalahan kompilasi:
Solusi berikut ini menarik: menambahkan cabang default
pasti akan memperbaiki kesalahan, tetapi ini bukan satu-satunya solusi - Anda masih dapat menambahkan case
untuk FALSE
.
Ya, kompiler akhirnya akan dapat menentukan apakah semua nilai enum tertutup (apakah semua opsi sudah habis), dan tidak menetapkan nilai default yang tidak berguna! Mari kita duduk sejenak dalam ucapan terima kasih sunyi.
Meskipun, ini masih menimbulkan satu pertanyaan. Bagaimana jika seseorang mengambil dan mengubah Bool gila menjadi Boolean angka empat dengan menambahkan nilai keempat? Jika Anda mengkompilasi ulang ekspresi sakelar untuk Bool yang diperluas, Anda akan mendapatkan kesalahan kompilasi (ekspresi tidak lagi lengkap). Tanpa kompilasi ulang, ini akan berubah menjadi masalah run-time. Untuk menangkap masalah ini, kompiler pergi ke cabang default
, yang berperilaku sama dengan yang kami gunakan sejauh ini, melempar pengecualian.
Di Java 12, merentang semua nilai tanpa cabang default
hanya berfungsi untuk enum
, tetapi ketika switch
menjadi lebih kuat di versi Java yang akan datang, ia juga bisa bekerja dengan tipe arbitrer. Jika label case
tidak hanya dapat memverifikasi kesetaraan, tetapi juga membuat perbandingan (misalnya, _ <5 -> ...) - ini akan mencakup semua opsi untuk jenis numerik.
Berpikir
Kami belajar dari artikel bahwa Java 12 mengubah switch
menjadi ekspresi, memberikannya fitur baru:
- sekarang satu
case
dapat sesuai dengan beberapa label; - Bentuk tanda panah baru
case … -> …
mengikuti sintaks ekspresi lambda:
- operator atau blok tunggal diperbolehkan;
- melewati ke
case
berikutnya dicegah;
- sekarang seluruh ekspresi dievaluasi sebagai nilai, yang kemudian dapat ditugaskan ke variabel atau diteruskan sebagai bagian dari pernyataan yang lebih besar;
- multi ekspresi: jika tipe target diketahui, maka semua cabang harus sesuai dengannya. Kalau tidak, tipe tertentu didefinisikan yang cocok dengan semua cabang;
break
dapat mengembalikan nilai dari blok;- untuk ekspresi
switch
menggunakan enum
, kompiler memeriksa ruang lingkup semua nilainya. Jika default
ada, cabang ditambahkan yang melempar pengecualian.
Di mana itu akan membawa kita? Pertama, karena ini bukan versi terakhir dari switch
, Anda masih punya waktu untuk meninggalkan umpan balik pada milis Amber jika Anda tidak setuju dengan sesuatu.
Kemudian, dengan asumsi saklar tetap seperti saat ini, saya pikir bentuk panah akan menjadi opsi default baru. Tanpa melalui bagian ke case
berikutnya dan dengan ekspresi lambda singkat (sangat alami untuk memiliki kasus dan satu pernyataan dalam satu baris), switch
terlihat jauh lebih kompak dan tidak mempengaruhi keterbacaan kode. Saya yakin bahwa saya hanya akan menggunakan titik dua jika saya perlu melewati bagian ini.
Apa yang kamu pikirkan Puas dengan apa yang terjadi?