Tidak jauh dari itu, Java
versi ke 14 yang baru , yang berarti inilah saatnya untuk melihat fitur sintaks baru apa yang akan terkandung dalam versi Java ini. Salah satu kemungkinan sintaksis ini adalah
pencocokan pola dari tipe yang akan diimplementasikan menggunakan
instanceof
operator yang ditingkatkan (diperluas).
Hari ini saya ingin bermain-main dengan operator baru ini dan mempertimbangkan fitur kerjanya lebih detail. Karena pencocokan pola berdasarkan tipe belum memasuki repositori JDK utama, saya harus mengunduh repositori
dari proyek Amber , yang sedang mengembangkan konstruksi sintaksis Java baru, dan
mengkompilasi JDK dari repositori ini.
Jadi, hal pertama yang akan kita lakukan adalah memeriksa versi Java untuk memastikan bahwa kita benar-benar menggunakan JDK 14:
> java -version openjdk version "14-internal" 2020-03-17 OpenJDK Runtime Environment (build 14-internal+0-adhoc.osboxes.amber-amber) OpenJDK 64-Bit Server VM (build 14-internal+0-adhoc.osboxes.amber-amber, mixed mode, sharing)
Benar juga.
Sekarang kita akan menulis sepotong kode kecil dengan operator
instanceof
"lama" dan menjalankannya:
public class A { public static void main(String[] args) { new A().f("Hello, world!"); } public void f(Object obj) { if (obj instanceof String) { String str = (String) obj; System.out.println(str.toLowerCase()); } } }
> java A.java hello, world!
Itu bekerja. Ini adalah pemeriksaan tipe standar diikuti oleh pemain. Kami menulis konstruksi yang sama setiap hari, apa pun versi Java yang kami gunakan, setidaknya 1,0, setidaknya 13.
Tapi sekarang kita memiliki Java 14 di tangan kita, dan mari kita menulis ulang kode menggunakan
instanceof
operator yang ditingkatkan (saya akan menghilangkan baris kode berulang di masa depan):
if (obj instanceof String str) { System.out.println(str.toLowerCase()); }
> java --enable-preview --source 14 A.java hello, world!
Bagus Kode ini lebih bersih, lebih pendek, lebih aman dan lebih mudah dibaca. Ada tiga pengulangan kata String, satu menjadi. Perhatikan bahwa kami tidak lupa untuk menentukan argumen
--enable-preview --source 14
, as Operator baru adalah
fitur pratinjau . Selain itu, pembaca yang penuh perhatian mungkin memperhatikan bahwa kami menjalankan file sumber A.java secara langsung, tanpa kompilasi. Fitur ini
muncul di Java 11.
Mari kita coba menulis sesuatu yang lebih canggih dan menambahkan kondisi kedua yang menggunakan variabel yang baru saja dinyatakan:
if (obj instanceof String str && str.length() > 5) { System.out.println(str.toLowerCase()); }
Ini mengkompilasi dan bekerja. Tetapi bagaimana jika Anda menukar kondisi?
if (str.length() > 5 && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: cannot find symbol if (str.length() > 5 && obj instanceof String str) { ^
Kesalahan kompilasi. Yang diharapkan: variabel
str
belum dideklarasikan, yang berarti tidak dapat digunakan.
Ngomong-ngomong, bagaimana dengan mutabilitas? Apakah variabelnya final atau tidak? Kami mencoba:
if (obj instanceof String str) { str = "World, hello!"; System.out.println(str.toLowerCase()); }
A.java:8: error: pattern binding str may not be assigned str = "World, hello!"; ^
Ya, variabel terakhir. Ini berarti bahwa kata "variabel" tidak sepenuhnya benar di sini. Dan kompiler menggunakan istilah khusus "pola mengikat". Oleh karena itu, saya mengusulkan mulai sekarang untuk mengatakan bukan "variabel", tetapi "pola mengikat" (sayangnya, kata "mengikat" tidak diterjemahkan dengan sangat baik ke dalam bahasa Rusia).
Dengan mutabilitas dan terminologi beres. Mari kita bereksperimen lebih jauh. Bagaimana jika kita berhasil "memecahkan" kompiler?
Bagaimana jika Anda memberi nama variabel dan pola yang mengikat dengan nama yang sama?
if (obj instanceof String obj) { System.out.println(obj.toLowerCase()); }
A.java:7: error: variable obj is already defined in method f(Object) if (obj instanceof String obj) { ^
Masuk akal. Tumpang tindih variabel dari lingkup luar tidak berfungsi. Ini setara dengan seolah-olah kita baru saja memutus objek variabel
obj
kedua kalinya dalam cakupan yang sama.
Dan jika demikian:
if (obj instanceof String str && obj instanceof String str) { System.out.println(str.toLowerCase()); }
A.java:7: error: illegal attempt to redefine an existing match binding if (obj instanceof String str && obj instanceof String str) { ^
Kompiler sekuat beton.
Apa lagi yang bisa Anda coba? Mari kita bermain-main dengan cakupan. Jika penjilidan didefinisikan di cabang
if
, apakah itu akan didefinisikan di cabang
else
jika kondisinya terbalik?
if (!(obj instanceof String str)) { System.out.println("not a string"); } else { System.out.println(str.toLowerCase()); }
Itu berhasil. Kompiler tidak hanya dapat diandalkan, tetapi juga cerdas.
Dan jika demikian?
if (obj instanceof String str && true) { System.out.println(str.toLowerCase()); }
Berhasil lagi. Compiler dengan benar memahami bahwa kondisi bermuara pada
obj instanceof String str
sederhana dari
obj instanceof String str
.
Apakah benar-benar tidak mungkin untuk "memecahkan" kompiler?
Mungkin begitu?
if (obj instanceof String str || false) { System.out.println(str.toLowerCase()); }
A.java:8: error: cannot find symbol System.out.println(str.toLowerCase()); ^
Ya! Ini sudah terlihat seperti bug. Bagaimanapun, ketiga kondisi ini benar-benar setara:
obj instanceof String str
obj instanceof String str && true
obj instanceof String str || false
Di lain pihak, aturan pelingkupan aliran agak
tidak trivial , dan mungkin kasus seperti itu seharusnya tidak berhasil. Tetapi jika Anda melihat murni dari sudut pandang manusia, maka saya pikir ini adalah bug.
Tapi ayolah, mari kita coba yang lain. Apakah ini akan berhasil:
if (!(obj instanceof String str)) { throw new RuntimeException(); } System.out.println(str.toLowerCase());
Disusun. Ini bagus, karena kode ini setara dengan yang berikut:
if (!(obj instanceof String str)) { throw new RuntimeException(); } else { System.out.println(str.toLowerCase()); }
Dan karena kedua opsi tersebut setara, programmer berharap mereka bekerja dengan cara yang sama.
Bagaimana dengan bidang yang tumpang tindih?
public class A { private String str; public void f(Object obj) { if (obj instanceof String str) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } } }
Kompiler tidak bersumpah. Ini logis, karena variabel lokal selalu bisa tumpang tindih bidang. Rupanya, mereka juga memutuskan untuk tidak membuat pengecualian untuk binding pola. Di sisi lain, kode semacam itu agak rapuh. Satu gerakan ceroboh, dan Anda mungkin tidak memperhatikan bagaimana
if
cabang Anda rusak:
private boolean isOK() { return false; } public void f(Object obj) { if (obj instanceof String str || isOK()) { System.out.println(str.toLowerCase()); } else { System.out.println(str.toLowerCase()); } }
Kedua cabang sekarang menggunakan bidang
str
, yang mungkin tidak diharapkan oleh programmer yang lalai. Untuk mendeteksi kesalahan seperti sedini mungkin, gunakan inspeksi di IDE dan penyorotan sintaksis yang berbeda untuk bidang dan variabel. Saya juga merekomendasikan agar Anda selalu menggunakan kualifikasi
this
untuk bidang. Ini akan menambah lebih banyak keandalan.
Apa lagi yang menarik? Seperti contoh "lama", yang baru tidak pernah cocok dengan
null
. Ini berarti bahwa Anda selalu dapat mengandalkan fakta bahwa pengikat pola tidak pernah dapat menjadi
null
:
if (obj instanceof String str) { System.out.println(str.toLowerCase());
Omong-omong, menggunakan properti ini, Anda dapat mempersingkat rantai seperti:
if (a != null) { B b = a.getB(); if (b != null) { C c = b.getC(); if (c != null) { System.out.println(c.getSize()); } } }
Jika Anda menggunakan
instanceof
, maka kode di atas dapat ditulis ulang seperti ini:
if (a != null && a.getB() instanceof B b && b.getC() instanceof C c) { System.out.println(c.getSize()); }
Tulis di komentar apa pendapat Anda tentang gaya ini. Apakah Anda menggunakan idiom ini?
Bagaimana dengan obat generik?
import java.util.List; public class A { public static void main(String[] args) { new A().f(List.of(1, 2, 3)); } public void f(Object obj) { if (obj instanceof List<Integer> list) { System.out.println(list.size()); } } }
> java --enable-preview --source 14 A.java Note: A.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 3
Sangat menarik. Jika
instanceof
"lama" hanya mendukung
instanceof List
atau
instanceof List<?>
, Maka yang baru berfungsi dengan tipe tertentu. Kami sedang menunggu orang pertama jatuh ke dalam perangkap seperti itu:
if (obj instanceof List<Integer> list) { System.out.println("Int list of size " + list.size()); } else if (obj instanceof List<String> list) { System.out.println("String list of size " + list.size()); }
Mengapa ini tidak berhasil?Jawab: kurangnya generik terverifikasi di Jawa.
IMHO, ini masalah yang cukup serius. Di sisi lain, saya tidak tahu cara memperbaikinya. Sepertinya Anda harus mengandalkan inspeksi di IDE lagi.
Kesimpulan
Secara umum, tipe pencocokan pola yang baru berfungsi sangat keren. Operator
instanceof
ditingkatkan memungkinkan Anda untuk melakukan tidak hanya pengujian jenis, tetapi juga mendeklarasikan pengikat siap pakai dari jenis ini, menghilangkan kebutuhan untuk casting manual. Ini berarti bahwa akan ada lebih sedikit noise dalam kode, dan akan jauh lebih mudah bagi pembaca untuk membedakan logika yang bermanfaat. Misalnya, sebagian besar
equals()
implementasi dapat ditulis dalam satu baris:
public class Point { private final int x, y; โฆ @Override public int hashCode() { return Objects.hash(x, y); } @Override public boolean equals(Object obj) { return obj instanceof Point p && px == this.x && py == this.y; } }
Kode di atas dapat ditulis lebih pendek. Bagaimana?Menggunakan
entri yang juga akan dimasukkan dalam Java 14. Kami akan membicarakannya lain kali.
Di sisi lain, beberapa poin kontroversial menimbulkan pertanyaan kecil:
- Aturan cakupan tidak sepenuhnya transparan (contoh dengan
instanceof || false
). - Bidang yang tumpang tindih.
instanceof
dan generik.
Namun, ini adalah nitpicking lebih kecil daripada klaim serius. Semua dalam semua, manfaat besar dari operator
instanceof
baru pasti layak ditambahkan bahasa. Dan jika masih meninggalkan status pratinjau dan menjadi sintaks yang stabil, maka akan menjadi motivasi yang bagus untuk akhirnya meninggalkan Java 8 ke versi Java yang baru.
NB Saya memiliki
saluran di Telegram di mana saya menulis tentang berita Jawa. Saya mendorong Anda untuk berlangganan.