Kata kunci var sudah diperkenalkan pada modul Dasar Bahasa sebagai singkatan penulisan tipe variabel lokal. Topik ini melanjutkannya ke sisi yang lebih menentukan, yaitu keputusan memakai var di tempat yang membantu dan menghindarinya di tempat yang merusak keterbacaan. Pembahasan dimulai dari mekanisme type inference, yakni proses kompiler menyimpulkan tipe dari nilai yang mengisinya, lalu bergerak ke tipe generik yang panjang, perulangan, try-with-resources, parameter lambda, dan jebakan inferensi yang paling sering ditanyakan. Di akhir topik, deklarasi bertipe seperti HashMap<String, List<Integer>> dapat ditulis satu kali saja tanpa kehilangan kepastian tipenya.
Cara inferensi tipe menentukan tipe variabel#
Inferensi tipe pada var berjalan sepenuhnya saat kompilasi. Kompilator membaca ekspresi di sebelah kanan tanda sama dengan, menyimpulkan tipenya, lalu memperlakukan variabel itu persis seolah-olah tipenya ditulis eksplisit. Tidak ada pemeriksaan tipe yang berpindah ke saat program berjalan, dan berkas .class yang dihasilkan identik dengan hasil deklarasi eksplisit. Konsekuensinya, var bukan tipe dinamis, dan variabelnya tetap tidak menerima nilai bertipe lain sampai akhir umurnya.
Tipe yang disimpulkan mengikuti aturan literal yang sama seperti deklarasi biasa.
// tidak-dijalankan
var jumlah = 10; // int, bukan long
var harga = 10.0; // double, bukan float
var inisial = 'A'; // charKetiga baris di atas menghasilkan tipe yang sama persis dengan int jumlah = 10, double harga = 10.0, dan char inisial = 'A'. Angka tanpa akhiran dianggap int, bilangan desimal dianggap double, dan tanda kutip tunggal dianggap char. Kesimpulan ini penting ketika tipe menyempit atau melebar secara diam-diam, misalnya var total = 15000 yang menghasilkan int, padahal perhitungan berikutnya membutuhkan long.
Membantu pada tipe yang panjang dan berulang#
Manfaat var paling terasa ketika tipe di sebelah kanan panjang dan berlapis, khususnya tipe generik dengan parameter bertingkat. Menuliskan HashMap<String, List<Integer>> dua kali pada satu pernyataan memakan ruang dan menggandakan peluang salah ketik, padahal informasinya sama. Dengan var, tipe itu ditulis satu kali pada pembuatnya, dan kompiler menyimpulkan sisanya.
import java.util.HashMap;
import java.util.List;
public class DemoTipePanjang {
public static void main(String[] args) {
var indeks = new HashMap<String, List<Integer>>();
indeks.put("pantai", List.of(3, 7, 12));
indeks.put("hutan", List.of(5, 9));
for (var entri : indeks.entrySet()) {
System.out.println(entri.getKey() + " -> " + entri.getValue());
}
}
}hutan -> [5, 9]
pantai -> [3, 7, 12]Deklarasi var indeks menyembunyikan tipe HashMap<String, List<Integer>> yang sebenarnya, tetapi tipe itu tetap berlaku penuh. Metode put hanya menerima kunci String dan nilai List<Integer>, dan kompiler menolak pasangan bertipe lain seperti indeks.put("sungai", "deret"). Perulangan for-each yang memakai var entri juga tetap bertipe Map.Entry<String, List<Integer>>, sehingga entri.getKey() dan entri.getValue() tersedia tanpa cast apa pun.
var pada perulangan, try-with-resources, dan parameter lambda#
Selain variabel lokal biasa, var sah dipakai pada variabel perulangan for dan for-each, variabel sumber daya di try-with-resources, serta parameter lambda. Dua tempat pertama tersedia sejak Java 10, sedangkan parameter lambda mengikutinya lewat JEP 323 pada Java 11. Aturan satu-satunya pada lambda adalah konsistensi, yaitu bila satu parameter ditulis dengan var, seluruh parameternya harus ditulis dengan var. Lambda yang parameternya tidak diberi tipe sama sekali juga tetap sah.
Program berikut memakai var pada tiga konteks itu sekaligus, yaitu koleksi, for-each, dan sumber daya try-with-resources.
import java.io.BufferedReader;
import java.io.IOException;
import java.io.StringReader;
import java.util.HashMap;
public class DemoLaporanVar {
public static void main(String[] args) throws IOException {
var stok = new HashMap<String, Integer>();
stok.put("Pensil", 40);
stok.put("Buku", 15);
var total = 0;
for (var jumlah : stok.values()) {
total += jumlah;
}
System.out.println("Total stok: " + total);
for (var entri : stok.entrySet()) {
System.out.println(entri.getKey() + " : " + entri.getValue());
}
try (var pembaca = new BufferedReader(new StringReader("Baris pertama\nBaris kedua"))) {
var baris = pembaca.readLine();
while (baris != null) {
System.out.println("Dibaca: " + baris);
baris = pembaca.readLine();
}
}
}
}Total stok: 55
Buku : 15
Pensil : 40
Dibaca: Baris pertama
Dibaca: Baris keduaSetiap var pada program di atas menyimpulkan tipe yang panjang jika ditulis eksplisit. Variabel pembaca sebenarnya bertipe BufferedReader, dan inilah kasus yang paling sering dimanfaatkan, karena sumber daya try-with-resources kerap dibungkus beberapa lapis seperti new BufferedReader(new FileReader(...)). Menulis tipe bungkusnya satu kali sudah cukup, sementara perulangannya tetap terbaca sebagai perulangan atas nama barang dan jumlahnya.
Jebakan inferensi pada diamond kosong#
Kombinasi var dengan diamond operator kosong <> adalah sumber salah paham yang paling umum. Pada deklarasi eksplisit seperti List<String> daftar = new ArrayList<>(), tipe di sebelah kiri menjadi konteks yang dibaca diamond, sehingga hasilnya ArrayList<String>. Pada var, tidak ada tipe di sebelah kiri untuk dibaca, sehingga diamond kosong jatuh ke tipe elemen paling umum, yaitu Object.
import java.util.ArrayList;
public class DemoDiamondKosong {
public static void main(String[] args) {
var daftar = new ArrayList<>(); // dianggap ArrayList<Object>
daftar.add("teks pertama");
daftar.add(42);
for (var item : daftar) {
System.out.println(item.getClass().getSimpleName() + " -> " + item);
}
}
}String -> teks pertama
Integer -> 42Keluaran menunjukkan daftar menerima String dan Integer sekaligus, bukti bahwa tipenya ArrayList<Object> dan bukan ArrayList<String>. Programnya tetap berjalan, sehingga kesalahpahaman ini tidak berhenti di kompilasi dan baru terasa ketika elemen dipakai dengan tipenya yang seharusnya. Perbaikannya mudah, yaitu menuliskan argumen tipe pada pembuatnya, seperti new ArrayList<String>(), atau mengembalikan deklarasi eksplisit di sebelah kiri.
Jebakan inferensi yang lain bersifat lebih terlihat. Inisialisasi array dengan kurung kurawal tidak memiliki tipe sendiri, sehingga var tidak punya dasar untuk menyimpulkan apa pun.
// tidak-dijalankan
var arr = {1, 2, 3};
// error: cannot infer type for local variable arr
// (array initializer needs an explicit target-type)Bentuk yang sah untuk kasus ini adalah var arr = new int[]{1, 2, 3}, karena pembuatnya menyebut tipe secara eksplisit. Aturan yang sama menjelaskan mengapa var tanpa pengisian awal dan var yang diisi null juga ditolak, sebagaimana sudah dipraktikkan pada topik konstanta dan literal.
Menimbang keterbacaan sebelum memakai var#
var yang tepat tempat membuat kode lebih pendek tanpa mengurangi informasi, sedangkan var yang salah tempat menyembunyikan tipe yang justru dibutuhkan pembaca. Ujian yang lazim dipakai adalah membaca deklarasi dari kanan, lalu menanyakan apakah tipenya langsung terlihat. Pemanggilan konstruktor seperti new BufferedReader(...) atau new ArrayList<String>() menyebut tipenya sendiri, sedangkan pemanggilan metode seperti proses(data) menyembunyikan tipe kembaliannya di tempat lain.
| Situasi | Sikap yang lazim |
|---|---|
| Pemanggilan konstruktor atau pabrik yang menyebut tipe | var aman dipakai |
| Tipe generik panjang yang akan ditulis dua kali | var membantu |
Hasil metode yang nama kembaliannya jelas, misalnya readLine() | var aman dipakai |
| Hasil metode yang tipenya tak terduga dari namanya | tulis tipe eksplisit |
Nilai int, long, atau double yang tipenya menentukan perilaku | tulis tipe eksplisit |
Penamaan variabel ikut menentukan. Nama seperti jumlahBaris atau daftarHadir membawa makna tipe di dalamnya, sedangkan nama seperti hasil atau data memaksa pembaca mencari tipe ke tubuh metode. Karena itu, keputusan memakai var paling aman dilakukan bersama kebiasaan menamai variabel secara spesifik, dan kebijakan tim tetap menjadi penentu akhir ketika dua pilihan sama sahnya.
Kesalahan umum#
- Memakai
varuntuk field kelas, parameter metode, atau tipe kembalian. Ketiganya ditolak kompiler dengan pesan'var' is not allowed here, karenavarmemang hanya dirancang untuk variabel lokal dan variabel perulangan. - Berpasangan dengan diamond kosong pada pembuat koleksi, seperti
var daftar = new ArrayList<>(). HasilnyaArrayList<Object>, dan elemen bertipe lain ikut diterima diam-diam. Tulisnew ArrayList<String>()bila memakaivar. - Mencampur parameter lambda bertipe dan ber-
var, misalnya(var a, int b) -> a + b. Kompilator menolak campuran itu, sehingga pilihannya hanya dua, yaitu semua parameter memakaivaratau tidak ada yang memakai. - Menyembunyikan tipe yang bermakna di balik nama samar. Deklarasi
var hasil = proses(data)memaksa pembaca membuka tubuhproseshanya untuk mengetahui tipenya. Tulis tipe eksplisit atau ubah nama variabel dan metodenya agar lebih spesifik.
Ringkasan#
- Inferensi tipe pada
varberjalan saat kompilasi dan menghasilkan tipe yang pasti, sehingga berkas.class-nya identik dengan deklarasi eksplisit. varsah untuk variabel lokal, variabelfordan for-each, variabel try-with-resources, dan parameter lambda, dengan syarat lambda memakainya secara konsisten di seluruh parameter.- Diamond kosong bersama
varmenyimpulkan tipe elemen paling umum, sehingganew ArrayList<>()menjadiArrayList<Object>, dan inisialisasi array{...}ditolak karena tidak membawa tipe. - Keputusan memakai
varpaling aman diuji dengan membaca deklarasi dari kanan, bila tipenya langsung terlihat,varmembantu, bila tidak, tipe eksplisit lebih dipilih.
Referensi#
- JEP 286: Local-Variable Type Inference — OpenJDK
— proposal resmi
var, termasuk daftar tempat yang boleh dan tidak boleh memakainya. - JEP 323: Local-Variable Syntax for Lambda Parameters — OpenJDK
— perluasan
varke parameter lambda pada Java 11. - Local Variable Type Inference Style Guidelines — OpenJDK
— pedoman resmi kapan
varmembantu dan kapan mengurangi keterbacaan.