Java Local Variable Type Inference (LVTI) atau, singkatnya, tipe var (pengidentifikasi var bukan kata kunci, tetapi nama tipe yang dicadangkan) ditambahkan ke Java 10 menggunakan JEP 286: Inferensi Tipe Variabel Lokal . Menjadi fungsi kompilator 100%, itu tidak mempengaruhi bytecode, runtime, atau kinerja. Pada dasarnya, kompiler memeriksa sisi kanan operator penugasan dan, berdasarkan hal itu, menentukan jenis variabel tertentu, dan kemudian menggantinya dengan var .
Selain itu, berguna untuk mengurangi verbositas kode boilerplate, dan juga mempercepat proses pemrograman itu sendiri. Sebagai contoh, sangat mudah untuk menulis var evenAndOdd =...
daripada Map<Boolean, List<Integer>> evenAndOdd =...
Munculnya var tidak berarti bahwa selalu dan nyaman untuk menggunakannya di mana-mana, kadang-kadang akan lebih praktis untuk dilakukan dengan alat standar.
Pada artikel ini, kita akan melihat 26 situasi, dengan contoh kapan Anda dapat menggunakan var , dan ketika itu tidak sepadan.
Butir 1: coba berikan nama yang bermakna ke variabel lokal
Biasanya kami fokus pada pemberian nama yang benar untuk bidang kelas, tetapi kami tidak memperhatikan nama variabel lokal. Ketika metode kami benar-benar diimplementasikan, mengandung sedikit kode dan memiliki nama baik, maka sangat sering kita tidak memperhatikan variabel lokal, atau bahkan sepenuhnya mengurangi nama mereka.
Ketika kita menggunakan var daripada menulis tipe eksplisit, kompiler secara otomatis mendeteksi mereka dan mengganti var . Tetapi di sisi lain, sebagai akibat dari ini, menjadi lebih sulit bagi orang untuk membaca dan memahami kode, karena menggunakan var dapat memperumit keterbacaan dan pemahamannya. Dalam kebanyakan kasus, ini karena kita cenderung melihat jenis variabel sebagai informasi primer, dan namanya sebagai sekunder. Meski seharusnya justru sebaliknya.
Contoh 1:
Banyak yang mungkin akan setuju bahwa dalam contoh di bawah ini, nama-nama variabel lokal terlalu pendek:
Saat menggunakan nama pendek, bersama dengan var , kode menjadi semakin tidak jelas:
Opsi yang lebih disukai:
Contoh 2:
Hindari penamaan variabel seperti ini:
Gunakan nama yang lebih bermakna:
Contoh 3:
Dalam upaya memberikan nama yang lebih komprehensif ke variabel lokal, jangan terlalu ekstrem:
Sebagai gantinya, Anda dapat menggunakan opsi yang lebih pendek, namun tidak kalah dimengerti:
Tahukah Anda bahwa Java memiliki kelas dalam bernama:
InternalFrameInternalFrameTitlePaneInternalFrameTitlePaneMaksimalkanButtonWindowNotFocusedState
Nah, penamaan variabel dengan tipe ini bisa rumit :)
Poin 2: gunakan literal untuk membantu var menentukan jenis primitif (int, panjang, float, dobel)
Tanpa menggunakan literal untuk tipe primitif, kita mungkin menemukan bahwa tipe yang diharapkan dan tersirat mungkin berbeda. Ini disebabkan oleh konversi tipe implisit yang digunakan oleh variabel var .
Misalnya, dua fragmen kode berikut berperilaku seperti yang diharapkan. Di sini kami secara eksplisit mendeklarasikan tipe boolean dan char :
boolean flag = true;
Sekarang kita menggunakan var , bukan tipe yang secara eksplisit menyatakan:
var flag = true;
Sejauh ini bagus. Sekarang lakukan hal yang sama untuk tipe int , long , float, dan double :
int intNumber = 20;
Meskipun potongan kode di atas sederhana dan mudah, sekarang mari kita gunakan var bukan tipe yang secara eksplisit menentukan.
Hindari:
Keempat variabel akan menjadi output sebagai int . Untuk memperbaiki perilaku ini, kita perlu menggunakan literal Java:
Tetapi apa yang terjadi jika kita mendeklarasikan angka desimal?
Hindari ini jika Anda berharap mendapatkan variabel tipe float :
Untuk menghindari kejutan, gunakan literal yang sesuai:
Butir 3: dalam beberapa kasus konversi tipe var dan implisit dapat menyederhanakan dukungan kode
Sebagai contoh, mari kita asumsikan bahwa kode kita ada di antara dua metode. Salah satu metode mendapatkan keranjang belanja dengan produk yang berbeda dan menghitung harga terbaik. Untuk melakukan ini, ia membandingkan berbagai harga di pasar dan mengembalikan harga total dalam bentuk tipe float . Metode lain hanya mengurangi harga ini dari kartu.
Pertama, mari kita lihat metode yang menghitung harga terbaik:
public float computeBestPrice(String[] items) { ... float price = ...; return price; }
Kedua, mari kita lihat metode yang bekerja dengan peta:
public boolean debitCard(float amount, ...) { ... }
Sekarang kami menempatkan kode kami di antara dua metode layanan eksternal ini sebagai klien. Pengguna kami dapat memilih barang untuk dibeli, dan kami menghitung harga terbaik untuk mereka, dan kemudian menghapus dana dari kartu:
Setelah beberapa waktu, perusahaan yang memiliki API memutuskan untuk meninggalkan representasi material dari harga yang mendukung desimal (bukan float , int sekarang digunakan). Jadi, mereka memodifikasi kode API sebagai berikut:
public int computeBestPrice(String[] items) { ... float realprice = ...; ... int price = (int) realprice; return price; } public boolean debitCard(int amount, ...) { ... }
Faktanya adalah bahwa kode kami menggunakan deklarasi eksplisit dari variabel float sebagai harga. Dalam bentuk saat ini, kami akan menerima kesalahan pada waktu kompilasi. Tetapi jika kita telah meramalkan situasi seperti itu dan menggunakan var alih-alih float , maka kode kita akan terus bekerja tanpa masalah, berkat konversi tipe implisit:
Butir 4: ketika literal bukan solusi yang cocok, gunakan casting eksplisit atau buang var
Beberapa tipe primitif di Jawa tidak memiliki literal khusus, misalnya tipe byte dan pendek . Dalam hal ini, menggunakan penunjukan tipe eksplisit, kita dapat membuat variabel tanpa masalah.
Gunakan ini sebagai ganti var :
Tetapi mengapa dalam situasi ini memberikan preferensi untuk notasi jenis eksplisit daripada hanya menggunakan var ? Baiklah, mari kita menulis kode ini menggunakan var . Perhatikan bahwa dalam kedua kasus, kompiler akan menganggap bahwa Anda memerlukan variabel tipe int .
Hindari kesalahan ini:
Tidak ada literal di sini yang akan membantu kami, oleh karena itu kami terpaksa menggunakan konversi tipe eksplisit ke bawah. Secara pribadi, saya akan menghindari situasi seperti itu, karena saya tidak melihat keuntungan apa pun di sini.
Gunakan hanya entri ini jika Anda benar-benar ingin menggunakan var :
Keuntungan menggunakan var adalah menulis kode yang lebih ringkas. Sebagai contoh, dalam kasus menggunakan konstruktor, kita dapat menghindari kebutuhan untuk mengulangi nama kelas dan, karenanya, menghilangkan redundansi kode.
Hindari yang berikut ini:
Gunakan sebaliknya:
Untuk konstruksi di bawah ini, var juga akan menjadi cara yang baik untuk menyederhanakan kode tanpa kehilangan informasi.
Hindari:
Gunakan kode berikut:
Jadi, mengapa kita lebih nyaman bekerja dengan var dalam contoh yang disajikan? Karena semua informasi yang diperlukan terkandung dalam nama-nama variabel. Tetapi jika var , dalam kombinasi dengan nama variabel, mengurangi kejelasan kode, lebih baik menolak untuk menggunakannya.
Hindari:
Gunakan:
Pertimbangkan, misalnya, penggunaan kelas java.nio.channels.Selector
. Kelas ini memiliki metode open()
statis yang mengembalikan Pemilih baru dan membukanya. Tetapi di sini Anda dapat dengan mudah berpikir bahwa metode Selector.open()
dapat mengembalikan tipe boolean , tergantung pada keberhasilan membuka pemilih yang ada, atau bahkan mengembalikan kekosongan . Menggunakan var di sini akan menyebabkan hilangnya informasi dan kebingungan dalam kode.
Butir 6: tipe var menjamin keamanan waktu kompilasi
Ini berarti bahwa kami tidak dapat mengkompilasi aplikasi yang mencoba melakukan tugas yang salah. Misalnya, kode di bawah ini tidak dapat dikompilasi:
Tetapi yang ini mengkompilasi:
var items = 10; items = 20;
Dan kode ini berhasil dikompilasi:
var items = "10"; items = "10 items";
Setelah kompiler menetapkan nilai variabel var , kami tidak dapat menetapkan apa pun selain tipe ini.
Butir 7: var tidak dapat digunakan untuk instantiate jenis tertentu dan menetapkannya ke variabel jenis antarmuka
Di Jawa, kami menggunakan pendekatan "pemrograman dengan antarmuka". Sebagai contoh, kami membuat turunan dari kelas ArrayList, mengaitkannya dengan abstraksi (antarmuka):
List<String> products = new ArrayList<>();
Dan kami menghindari hal-hal seperti mengikat objek ke variabel dengan tipe yang sama:
ArrayList<String> products = new ArrayList<>();
Ini adalah praktik yang paling umum dan diinginkan, karena kita dapat dengan mudah mengganti implementasi antarmuka dengan yang lain. Untuk ini, hanya perlu mendeklarasikan variabel tipe antarmuka.
Kami tidak akan dapat mengikuti konsep ini menggunakan variabel var, seperti tipe tertentu selalu ditampilkan untuk mereka. Misalnya, dalam cuplikan kode berikut, kompiler akan menentukan tipe variabel sebagai ArrayList<String>
:
var productList = new ArrayList<String>();
Ada beberapa argumen pembelaan yang menjelaskan perilaku ini:
var digunakan untuk variabel lokal, di mana, dalam kebanyakan kasus, pemrograman menggunakan antarmuka kurang dari dalam kasus dengan parameter metode yang dikembalikan oleh nilai atau bidang
Ruang lingkup variabel lokal harus kecil, sehingga menyelesaikan masalah yang disebabkan oleh beralih ke implementasi lain seharusnya tidak terlalu sulit
var memperlakukan kode di sebelah kanan sebagai inisialisasi yang digunakan untuk menentukan tipe aktual. Jika, pada beberapa titik, initializer diubah, maka jenis yang didefinisikan juga dapat berubah, menyebabkan masalah dalam kode yang bergantung pada variabel ini.
Paragraf 8: probabilitas kesimpulan dari tipe yang tidak terduga
Menggunakan var dalam kombinasi dengan operator intan (<>) tanpa adanya informasi untuk mengidentifikasi tipe dapat menyebabkan hasil yang tidak terduga.
Sebelum Java 7, inferensi tipe eksplisit digunakan untuk koleksi:
Dimulai dengan Java 7, operator berlian diperkenalkan. Dalam hal ini, kompiler akan secara independen mendapatkan tipe yang diperlukan:
Jenis apa yang akan dihasilkan dalam kode di bawah ini?
Anda harus menghindari konstruksi tersebut:
Jenis akan didefinisikan sebagai ArrayList<Object>
. Ini karena informasi yang diperlukan untuk menentukan dengan benar jenis tidak disediakan. Ini mengarah pada fakta bahwa tipe terdekat akan dipilih, yang dapat kompatibel dengan konteks apa yang terjadi. Dalam hal ini, Object
.
Dengan demikian, var hanya dapat digunakan jika kami memberikan informasi yang diperlukan untuk menentukan jenis yang diharapkan. Jenisnya dapat ditentukan secara langsung atau diteruskan sebagai argumen.
Tentukan jenisnya secara langsung:
Lewati argumen dari jenis yang diperlukan:
var productStack = new ArrayDeque<String>(); var productList = new ArrayList<>(productStack);
Product p1 = new Product(); Product p2 = new Product(); var listOfProduct = List.of(p1, p2);
Butir 9: menugaskan array ke variabel var tidak memerlukan tanda kurung []
Kita semua tahu cara mendeklarasikan array di Jawa:
int[] numbers = new int[5];
Bagaimana dengan menggunakan var saat bekerja dengan array? Dalam hal ini, tidak perlu menggunakan tanda kurung di sisi kiri.
Hindari yang berikut ini (ini bahkan tidak dapat dikompilasi):
Gunakan:
Kode di bawah ini menggunakan var juga gagal dikompilasi. Ini karena kompiler tidak dapat menentukan jenis dari sisi kanan:
Butir 10: var tidak dapat digunakan saat mendeklarasikan beberapa variabel pada baris yang sama
Jika Anda ingin mendeklarasikan variabel dari tipe yang sama sekaligus, maka Anda perlu tahu bahwa var tidak cocok untuk ini. Kode berikut tidak dikompilasi:
Gunakan sebaliknya:
Atau apakah itu:
Butir 11: variabel lokal harus berusaha meminimalkan ruang lingkupnya. Tipe var memperkuat pernyataan ini.
Simpan cakupan kecil untuk variabel lokal - Saya yakin Anda pernah mendengar pernyataan ini sebelum var .
Keterbacaan dan perbaikan bug cepat adalah argumen yang mendukung pendekatan ini. Misalnya, mari kita tentukan tumpukan sebagai berikut:
Hindari ini:
Perhatikan bahwa kita memanggil metode forEach()
, yang diwarisi dari java.util.Vector
. Metode ini akan melalui tumpukan seperti vektor lainnya dan inilah yang kami butuhkan. Tapi sekarang kami memutuskan untuk menggunakan ArrayDeque
daripada Stack
. Ketika kita melakukan ini, metode forEach()
akan menerima implementasi dari ArrayDeque yang akan melintasi stack sebagai stack standar (LIFO)
Ini bukan yang kita inginkan. Terlalu sulit untuk melacak kesalahan di sini, karena kode yang berisi bagian forEach()
tidak terletak di sebelah kode tempat perubahan dilakukan. Untuk meningkatkan kecepatan pencarian dan memperbaiki kesalahan, lebih baik menulis kode menggunakan variabel stack
, sedekat mungkin dengan deklarasi variabel ini.
Ini paling baik dilakukan sebagai berikut:
Sekarang, ketika pengembang beralih dari Stack
ke ArrayQueue
, ia akan dapat dengan cepat melihat kesalahan dan memperbaikinya.
Klausul 12: tipe var menyederhanakan penggunaan berbagai tipe dalam operator ternary
Kita dapat menggunakan berbagai jenis operan di sisi kanan operator ternary.
Saat menentukan jenis secara eksplisit, kode berikut ini tidak dapat dikompilasi:
Namun demikian, kita dapat melakukan ini:
Collection code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10); Object code = containsDuplicates ? List.of(12, 1, 12) : Set.of(12, 1, 10);
Kode di bawah ini juga tidak dapat dikompilasi:
Tetapi Anda dapat menggunakan jenis yang lebih umum:
Serializable code = intOrString ? 12112 : "12112"; Object code = intOrString ? 12112 : "12112";
Dalam semua kasus seperti itu, lebih baik untuk memilih var :
Ini tidak mengikuti dari contoh-contoh ini bahwa tipe var mendefinisikan tipe objek saat runtime. Ini tidak benar!
Dan, tentu saja, tipe var akan bekerja dengan benar dengan tipe yang sama dari kedua operan:
Poin 13: tipe var dapat digunakan di dalam loop
Kita dapat dengan mudah mengganti deklarasi eksplisit tipe dalam untuk loop dengan tipe var .
Mengubah tipe int eksplisit ke var :
Mengubah jenis Order
menjadi var :
List<Order> orderList = ...;
Butir 14: var berfungsi baik dengan stream di Java 8
Sangat mudah untuk menggunakan var dari Java 10 dengan stream yang muncul di Java 8.
Anda cukup mengganti deklarasi eksplisit tipe stream dengan var :
Contoh 1:
Contoh 2:
Klausul 15: var dapat digunakan ketika mendeklarasikan variabel lokal yang dimaksudkan untuk memecah rantai ekspresi menjadi beberapa bagian
Ekspresi dengan banyak bersarang terlihat mengesankan dan biasanya tampak seperti semacam potongan kode yang cerdas dan penting. Dalam kasus di mana perlu untuk memfasilitasi keterbacaan kode, disarankan untuk memecah ekspresi besar menggunakan variabel lokal. Tetapi kadang-kadang menulis banyak variabel lokal sepertinya pekerjaan yang sangat melelahkan yang ingin saya hindari.
Contoh ekspresi besar:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Lebih baik memecah kode menjadi bagian-bagian komponennya:
List<Integer> intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Versi kode yang kedua terlihat lebih mudah dibaca dan lebih sederhana, tetapi versi pertama juga memiliki hak untuk ada. Sangatlah normal bagi pikiran kita untuk beradaptasi dengan pemahaman ekspresi yang begitu besar dan lebih menyukai variabel lokal. Namun, menggunakan tipe var dapat membantu memecah struktur besar dengan mengurangi upaya untuk mendeklarasikan variabel lokal:
var intList = List.of(1, 1, 2, 3, 4, 4, 6, 2, 1, 5, 4, 5);
Klausa 16: var tidak dapat digunakan sebagai tipe kembali atau sebagai tipe argumen metode
Dua cuplikan kode yang ditunjukkan di bawah ini tidak dapat dikompilasi.
Menggunakan var sebagai tipe pengembalian:
Menggunakan var sebagai jenis argumen metode:
Klausa 17: variabel lokal dari tipe var dapat diteruskan sebagai parameter metode atau mereka dapat mengambil nilai yang dikembalikan oleh metode
Fragmen kode berikut ini akan dikompilasi dan akan berfungsi dengan baik:
public int countItems(Order order, long timestamp) { ... } public boolean checkOrder() { var order = ...;
:
public <A, B> B contains(A container, B tocontain) { ... } var order = ...;
18: var
:
public interface Weighter { int getWeight(Product product); }
var :
public interface Weighter { int getWeight(Product product); }
19: var effectively final
, :
โฆ Java SE 8, , final effectively final. , , effectively final .
, var effectively final. .
:
public interface Weighter { int getWeight(Product product); }
:
public interface Weighter { int getWeight(Product product); }
20: var- final-
var ( , effectively final). , final .
:
:
21:
var , . , var , :
:
Java 11 var - . Java 11:
22: var null'
var - .
( null ):
( ):
:
23: var
var , .
:
:
24: var catch
, try-with-resources
catch
, , .
:
:
Try-with-resources
, var try-with-resources .
, :
var :
25: var
, :
public <T extends Number> T add(T t) { T temp = t; ... return temp; }
, var , T var :
public <T extends Number> T add(T t) { var temp = t; ... return temp; }
, var :
codepublic <T extends Number> T add(T t) { List<T> numbers = new ArrayList<>(); numbers.add((T) Integer.valueOf(3)); numbers.add((T) Double.valueOf(3.9)); numbers.add(t); numbers.add("5");
List<T> var :
public <T extends Number> T add(T t) { var numbers = new ArrayList<T>();
26: var Wildcards (?),
? Wildcards
var :
Foo<?> var , , var .
, , , , . , ArrayList , Collection<?> :
(Foo <? extends T>) (Foo <? super T>)
, :
, , :
var :
, . โ :
Kesimpulan
ยซ var ยป, Java 10. , . , var , .
var Java!