Sistem null-safety Kotlin sempurna hanya ketika seluruh kode ditulis dalam Kotlin. Saat kita memanggil kode Java — misalnya standard library Java, framework Spring, atau library Android — kompilator Kotlin tidak memiliki informasi nullability yang lengkap. Kompilator menandai tipe dari Java sebagai platform type (ditulis T! secara internal) yang berarti tipe tersebut bisa nullable maupun non-nullable — kompilator tidak tahu pasti. Pada chapter ini kita akan membedah perilaku platform types, risiko yang ditimbulkannya, dan strategi mitigasi yang dianjurkan.

Apa Itu Platform Type #

Platform type muncul karena Java tidak memiliki anotasi nullability bawaan pada tipe referensi. Saat method Java String getNama() dipanggil dari Kotlin, kompilator tidak tahu apakah nilai kembalian bisa null atau tidak. Karena itu, kompilator memperlakukannya sebagai T! — sebuah tipe khusus yang dapat ditugaskan ke T (non-nullable) atau T? (nullable) tanpa peringatan.

Hal ini bermakna ganda:

  • Memberi fleksibilitas: kita dapat menetapkan hasil panggilan Java ke variabel non-nullable tanpa konversi eksplisit.
  • Membuka celah NPE: bila ternyata nilai dari Java adalah null namun kita tetap memperlakukannya sebagai non-nullable, NullPointerException akan muncul saat runtime.

Demonstrasi Platform Type #

Berikut adalah contoh yang memperlihatkan perilaku platform type menggunakan Java standard library. Method seperti System.getProperty(key), Properties.getProperty(key), dan HashMap.get(key) semuanya mengembalikan tipe platform dari sudut pandang Kotlin.

kotlin
fun main() {
    val properti = java.util.Properties()
    properti.setProperty("host", "localhost")
    properti.setProperty("port", "8080")

    val host: String? = properti.getProperty("host")
    println("Host: ${host?.length}")

    val tidakAda: String? = properti.getProperty("tidak_ada")
    println("Properti tidak_ada: $tidakAda")
    println("Panjang (pakai safe call): ${tidakAda?.length}")

    val peta = java.util.HashMap<String, String>()
    peta["nama"] = "Budi"

    val nama: String? = peta["nama"]
    println("Nama dari HashMap: ${nama?.uppercase()}")

    val umur: String? = peta["umur"]
    println("Umur dari HashMap: $umur")
    val panjangUmur: Int = umur?.length ?: 0
    println("Panjang umur default: $panjangUmur")

    val envPath: String? = System.getProperty("line.separator")
    println("Line separator ditemukan: ${envPath != null}")

    val tokenAman: String = System.getProperty("app.token") ?: "DEFAULT_TOKEN"
    println("Token aplikasi: $tokenAman")

    val sumber: String? = "Budi"
    val panjangAman: Int = sumber?.length ?: 0
    println("Panjang sumber: $panjangAman")

    val pembatas: String = System.getProperty("file.separator") ?: "/"
    println("Pembatas file: $pembatas")

    val daftarHasil: List<String> = listOf("a", null, "b").mapNotNull { it?.uppercase() }
    println("Daftar hasil tanpa null: $daftarHasil")

    val konfigurasi: Map<String, String?> = mapOf("host" to "localhost", "port" to null)
    val portFinal: String = konfigurasi["port"] ?: "8080"
    println("Port final: $portFinal")

    val daftarJava = java.util.ArrayList<String>()
    daftarJava.add("Bandung")
    val elemen: String? = if (daftarJava.isNotEmpty()) daftarJava[0] else null
    println("Elemen dari ArrayList: $elemen")

    val hasilAkhir: String = peta["tidak_ada"] ?: run {
        println("Menggunakan nilai fallback karena kunci tidak ada")
        "fallback"
    }
    println("Hasil akhir: $hasilAkhir")
}

Output yang Dihasilkan:

text
Host: 9
Properti tidak_ada: null
Panjang (pakai safe call): null
Nama dari HashMap: BUDI
Umur dari HashMap: null
Panjang umur default: 0
Line separator ditemukan: true
Token aplikasi: DEFAULT_TOKEN
Panjang sumber: 4
Pembatas file: /
Daftar hasil tanpa null: [A, B]
Port final: 8080
Elemen dari ArrayList: Bandung
Menggunakan nilai fallback karena kunci tidak ada
Hasil akhir: fallback

Mari kita uraikan baris-baris penting pada contoh di atas:

  1. Baris 5 (properti.getProperty("host")): walaupun secara statis bertipe platform String!, kita deklarasikan variabel sebagai String? eksplisit. Strategi ini aman — kompilator mengizinkan penetapan platform type ke nullable, dan kita terhindar dari risiko NPE.
  2. Baris 9 (properti.getProperty("tidak_ada")): kunci yang tidak ada membuat method Java mengembalikan null. Karena kita sudah mengetik variabel sebagai String?, hasilnya konsisten null tanpa kejutan.
  3. Baris 13 (peta["nama"]): java.util.HashMap.get() mengembalikan V!. Tetap diperlakukan sebagai nullable sehingga .uppercase() dipanggil via safe call ?..
  4. Baris 19 (System.getProperty("line.separator")): System.getProperty() adalah salah satu method Java paling sering dipakai. Hasilnya platform type. Karena itu deklarasikan sebagai String? eksplisit.
  5. Baris 22 (System.getProperty("app.token") ?: "DEFAULT_TOKEN"): pola yang aman untuk membaca konfigurasi dari sistem Java. Operator Elvis memberi nilai pengganti bila properti tidak terdaftar.
  6. Baris 28 (System.getProperty("file.separator") ?: "/"): bahkan untuk properti yang biasanya selalu ada (seperti file.separator yang bernilai / di Linux), tetap disarankan menambahkan Elvis sebagai fallback. Hal ini membuat kode lebih portable antar platform.
  7. Baris 40 (peta["tidak_ada"] ?: run { ... }): pola lebih kompleks dengan run di sisi kanan Elvis. Saat HashMap tidak mengandung kunci, blok run dieksekusi untuk menghasilkan nilai fallback sekaligus mencatat log.

Strategi Mitigasi #

Berikut tiga strategi yang dianjurkan untuk menangani platform types:

StrategiCaraKelebihan
Tipe eksplisit nullableDeklarasikan variabel sebagai T?Aman, eksplisit, mengikuti asumsi pesimis
Anotasi nullability JavaTambahkan @Nullable/@NotNull pada kode JavaKompilator Kotlin membaca anotasi dan memperlakukan tipe sesuai
Wrapper KotlinBungkus pemanggilan Java dalam function Kotlin yang mengembalikan T?Memisahkan batas Java-Kotlin menjadi jelas

Referensi Lanjutan #

  • Null-safety — bagian platform types dari kode Java pada panduan null-safety

Setelah memahami platform types, langkah berikutnya adalah menyusun rangkuman praktik terbaik untuk penanganan nullable di seluruh kodebase Kotlin — topik yang akan kita bahas pada chapter Null Safety Best Practices.