var dan Type Inference

Memakai var untuk tipe lokal di tempat yang membantu dan menghindari tempat yang merusak keterbacaan.

Daftar isi

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.

java Contoh: tipe yang disimpulkan dari literal
// tidak-dijalankan
var jumlah = 10;   // int, bukan long
var harga = 10.0;  // double, bukan float
var inisial = 'A'; // char

Ketiga 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.

java DemoTipePanjang.java
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());
        }
    }
}
text Keluaran
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.

java DemoLaporanVar.java
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();
            }
        }
    }
}
text Keluaran
Total stok: 55
Buku : 15
Pensil : 40
Dibaca: Baris pertama
Dibaca: Baris kedua

Setiap 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.

java DemoDiamondKosong.java
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);
        }
    }
}
text Keluaran
String -> teks pertama
Integer -> 42

Keluaran 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.

java Contoh: array initializer tanpa tipe ditolak
// 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.

SituasiSikap yang lazim
Pemanggilan konstruktor atau pabrik yang menyebut tipevar aman dipakai
Tipe generik panjang yang akan ditulis dua kalivar membantu
Hasil metode yang nama kembaliannya jelas, misalnya readLine()var aman dipakai
Hasil metode yang tipenya tak terduga dari namanyatulis tipe eksplisit
Nilai int, long, atau double yang tipenya menentukan perilakutulis 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 var untuk field kelas, parameter metode, atau tipe kembalian. Ketiganya ditolak kompiler dengan pesan 'var' is not allowed here, karena var memang hanya dirancang untuk variabel lokal dan variabel perulangan.
  • Berpasangan dengan diamond kosong pada pembuat koleksi, seperti var daftar = new ArrayList<>(). Hasilnya ArrayList<Object>, dan elemen bertipe lain ikut diterima diam-diam. Tulis new ArrayList<String>() bila memakai var.
  • 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 memakai var atau tidak ada yang memakai.
  • Menyembunyikan tipe yang bermakna di balik nama samar. Deklarasi var hasil = proses(data) memaksa pembaca membuka tubuh proses hanya untuk mengetahui tipenya. Tulis tipe eksplisit atau ubah nama variabel dan metodenya agar lebih spesifik.

Ringkasan#

  • Inferensi tipe pada var berjalan saat kompilasi dan menghasilkan tipe yang pasti, sehingga berkas .class-nya identik dengan deklarasi eksplisit.
  • var sah untuk variabel lokal, variabel for dan for-each, variabel try-with-resources, dan parameter lambda, dengan syarat lambda memakainya secara konsisten di seluruh parameter.
  • Diamond kosong bersama var menyimpulkan tipe elemen paling umum, sehingga new ArrayList<>() menjadi ArrayList<Object>, dan inisialisasi array {...} ditolak karena tidak membawa tipe.
  • Keputusan memakai var paling aman diuji dengan membaca deklarasi dari kanan, bila tipenya langsung terlihat, var membantu, bila tidak, tipe eksplisit lebih dipilih.

Referensi#