Platform type T! adalah sumber NullPointerException tersekat karena Kotlin tidak tahu apakah method Java dapat mengembalikan null atau tidak. Solusi permanennya adalah menambahkan anotasi nullability di sisi Java — @Nullable, @NotNull, atau JSR-305. Bab ini menjelaskan anotasi yang dibaca Kotlin, cara kerjanya, dan dampaknya pada kode Kotlin yang mengonsumsi method Java beranotasi.

Anotasi yang Dibaca Kotlin #

Kotlin mengenali beberapa jenis anotasi nullability:

Sumber AnotasiPaketContoh
JetBrainsorg.jetbrains.annotations@Nullable, @NotNull
JSR-305javax.annotation@Nullable, @Nonnull, @ParametersAreNonnullByDefault
AndroidXandroidx.annotation@Nullable, @NonNull
Spring Frameworkorg.springframework.lang@Nullable, @NonNull
FindBugs/SpotBugsedu.umd.cs.findbugs.annotations@Nullable, @NonNull, @CheckForNull
JDK internaljdk.internal (via @Nullable di source)Anotasi pada Map.get, dll.

Saat kompilator Kotlin melihat salah satu anotasi ini pada method Java, tipe kembalian diproses: @Nullable menjadi T?, @NotNull menjadi T. Tanpa anotasi, tipe tetap menjadi platform type T!.

Cara Kerja: Sebelum vs Sesudah Anotasi #

Tabel berikut membandingkan kode Java tanpa dan dengan anotasi, beserta tipe yang dilihat Kotlin:

java Java tanpa anotasi
public class Repo {
    public String cari(String id) { ... }
}
Sisi KotlinTipeRisiko
repo.cari("x") ditugaskan ke Stringplatform String!Bisa null, tidak ada peringatan
repo.cari("x") ditugaskan ke String?String?Aman
java Java dengan anotasi
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.NotNull;

public class Repo {
    @Nullable
    public String cariOpsional(String id) { ... }

    @NotNull
    public String cariWajib(String id) { ... }
}
Sisi KotlinTipeRisiko
repo.cariOpsional("x")String?Eksplisit nullable, aman
repo.cariWajib("x")StringEksplisit non-null, aman

Contoh Lengkap: Efek Anotasi JDK Bawaan #

Kode berikut memakai kelas JDK yang sudah mengandung anotasi nullability (modern JDK mulai menambahkan @Nullable pada method seperti Map.get). Efeknya, Kotlin memperlakukan tipe kembalian sebagai String? tanpa perlu deklarasi eksplisit:

kotlin
import java.util.HashMap
import java.util.ArrayList

// Demonstrasi cara Kotlin membaca anotasi nullability dari JDK.
// JDK modern mengannotasi method seperti Map.get dengan @Nullable
// (melalui jsr-305 yang dibungkus jdk.internal), sehingga Kotlin
// memperlakukan tipe kembalian sebagai nullable.
//
// Anotasi yang dibaca Kotlin:
// - @org.jetbrains.annotations.Nullable  -> T?
// - @org.jetbrains.annotations.NotNull   -> T
// - @javax.annotation.Nullable (JSR-305) -> T?  (bila library tersedia)
// - @javax.annotation.Nonnull (JSR-305)  -> T   (bila library tersedia)
//
// File ini memakai kelas JDK yang sudah memiliki anotasi built-in
// untuk mendemonstrasikan efeknya dari sisi Kotlin.

fun main() {
    // 1. HashMap.get(): JDK mengannotasi @Nullable -> Kotlin String?
    val peta: HashMap<String, String> = HashMap()
    peta["nama"] = "Budi"

    val namaAman: String? = peta["nama"]
    val namaTidakAda: String? = peta["tidakAda"]
    println("=== HashMap.get(): @Nullable dari JDK ===")
    println("namaAman     : $namaAman (tipe: ${if (namaAman == null) "null" else "non-null"})")
    println("namaTidakAda : $namaTidakAda (tipe: ${if (namaTidakAda == null) "null" else "non-null"})")

    // 2. HashMap.put(): mengembalikan nilai LAMA yang asosiasi dengan kunci,
    //    atau null bila tidak ada. Anotasi @Nullable -> V?
    val lama: String? = peta.put("nama", "Siti")
    println()
    println("=== HashMap.put(): nilai lama ===")
    println("nilai lama untuk 'nama': $lama")

    val lama2: String? = peta.put("baru", "NilaiBaru")
    println("nilai lama untuk 'baru': $lama2 (null = tidak ada)")

    // 3. ArrayList.remove(int): mengembalikan elemen yang dihapus (non-null bila E non-null)
    val daftar: ArrayList<String> = ArrayList()
    daftar.add("Pertama")
    daftar.add("Kedua")
    val dihapus: String = daftar.removeAt(0)  // tidak nullable karena E = String non-null
    println()
    println("=== ArrayList.removeAt(): non-null ===")
    println("dihapus      : $dihapus")
    println("daftar sekarang: $daftar")

    // 4. Map.getOrDefault: anotasi @Nullable pada parameter default, @NotNull pada return
    val nilaiDefault: String = peta.getOrDefault("tidakAda", "default")
    println()
    println("=== getOrDefault: non-null ===")
    println("nilaiDefault : $nilaiDefault")

    // 5. Contoh perbandingan: method Java tanpa anotasi jadi platform type
    println()
    println("=== Method tanpa anotasi: platform type ===")
    val berkas = java.io.File("/tmp/nullability_demo.txt")
    berkas.writeText("hello")
    val path: String? = berkas.absolutePath  // File.getAbsolutePath() tidak @Nullable, jadi String!
    val pathNonNull: String = berkas.absolutePath  // aman karena platform type, bisa non-null
    println("absolutePath : $path")
    println("nonNullPath  : $pathNonNull")
    berkas.delete()
}

Output yang Dihasilkan:

text
=== HashMap.get(): @Nullable dari JDK ===
namaAman     : Budi (tipe: non-null)
namaTidakAda : null (tipe: null)

=== HashMap.put(): nilai lama ===
nilai lama untuk 'nama': Budi
nilai lama untuk 'baru': null (null = tidak ada)

=== ArrayList.removeAt(): non-null ===
dihapus      : Pertama
daftar sekarang: [Kedua]

=== getOrDefault: non-null ===
nilaiDefault : default

=== Method tanpa anotasi: platform type ===
absolutePath : /tmp/nullability_demo.txt
nonNullPath  : /tmp/nullability_demo.txt

Mari kita telaah baris-baris kode di atas:

  1. Baris 23 (val namaAman: String? = peta["nama"]): HashMap.get(K) dianotasi @Nullable di JDK modern. Kotlin membaca anotasi tersebut dan memperlakukan tipe kembalian sebagai V? (yaitu String?). Tanpa deklarasi String? eksplisit, kompilator tetap menerima nilai sebagai platform type — namun anotasi memberi sinyal kuat yang dipakai Kotlin saat inferensi tipe.
  2. Baris 33 (val lama: String? = peta.put("nama", "Siti")): HashMap.put juga @Nullable karena mengembalikan nilai lama yang mungkin null (bila kunci baru). Kotlin memperlakukan hasil sebagai String? tanpa ambiguitas.
  3. Baris 41 (val dihapus: String = daftar.removeAt(0)): ArrayList.remove(int) dianotasi @NotNull untuk elemen generik non-null. Karena ArrayList<String> (bukan ArrayList<String?>), Kotlin memperlakukan hasil sebagai String non-nullable. Penugasan ke variabel String langsung berhasil.
  4. Baris 48 (val nilaiDefault: String = peta.getOrDefault(...)): getOrDefault mengembalikan nilai default non-null bila disediakan. JDK mengannotasi tipe kembalian sebagai @NotNull karena kontraknya jelas: salah satu dari nilai aktual atau default, keduanya non-null.
  5. Baris 55-56 (File.getAbsolutePath): method ini tidak dianotasi @Nullable maupun @NotNull di JDK. Hasilnya adalah platform type String!. Kita bisa menugaskan ke String? maupun String — kompilator menerima keduanya, namun bila method Java ternyata mengembalikan null, kode Kotlin akan melempar NPE saat runtime.

Anotasi Level Paket (JSR-305) #

JSR-305 menyediakan anotasi @ParametersAreNonnullByDefault yang dapat dipasang pada level paket Java, sehingga semua parameter di paket tersebut dianggap @Nonnull kecuali diberi anotasi @Nullable eksplisit. Kotlin membaca anotasi ini dan menerapkan nullability default ke seluruh paket.

java Java: anotasi default level paket
@ParametersAreNonnullByDefault
package com.example.repo;

import javax.annotation.ParametersAreNonnullByDefault;
import javax.annotation.Nullable;

public class Repo {
    public String cari(String id) { ... }             // NonNull
    public @Nullable String cariOpsional(String id) { ... }  // Nullable
}

Referensi Lanjutan #

Selanjutnya #

Nullability adalah satu aspek dari translasi tipe antar Java dan Kotlin. Aspek lain yang sama pentingnya adalah translasi getter/setter Java menjadi properti Kotlin. Bab Java Properties dan Kotlin Properties akan membahas aturan sintaks properti yang dihasilkan kompilator Kotlin dari method getXxx/setXxx Java.