Java dan Kotlin sama-sama berjalan di atas JVM, tetapi keduanya memberi pengalaman menulis kode yang sangat berbeda. Perbandingan langsung membantu kita memahami apa yang sebenarnya berubah saat bermigrasi, dan apa yang tetap bisa kita andalkan. Pada chapter ini kita akan melihat enam titik perbedaan utama dengan contoh kode yang setara, lalu menutupnya dengan diskusi interoperabilitas dua arah.

Data Class #

Salah satu perbedaan paling terasa adalah deklarasi class data. Padanannya di Java membutuhkan constructor, getter, equals, hashCode, dan toString. Di Kotlin, satu baris sudah cukup:

kotlin Kotlin: satu baris data class
data class Pengguna(val nama: String, val umur: Int)
java Java: butuh boilerplate sekitar 40 baris
public final class Pengguna {
    private final String nama;
    private final int umur;

    public Pengguna(String nama, int umur) {
        this.nama = nama;
        this.umur = umur;
    }

    public String getNama() { return nama; }
    public int getUmur() { return umur; }

    @Override
    public boolean equals(Object o) { /* implementasi */ }

    @Override
    public int hashCode() { /* implementasi */ }

    @Override
    public String toString() { /* implementasi */ }
}

Selisih baris ini akan terasa nyata ketika sebuah proyek memiliki puluhan model domain.

Null-Safety #

Java membiarkan setiap reference menampung null, sehingga NullPointerException lazim dijumpai di runtime. Kotlin memisahkan tipe nullable (String?) dari non-nullable (String) di level kompilator:

kotlin Kotlin: pemisahan eksplisit tipe nullable
val a: String = "halo"      // tidak bisa null
val b: String? = null      // boleh null
// a = null  // galat kompilasi

Kompilator memaksa kita menangani null secara eksplisit lewat operator ?. (safe call), ?: (Elvis), atau !! (non-null assertion).

Extension Function #

Di Java, menambahkan method baru pada class pihak ketiga butuh subclassing atau class utilitas static. Kotlin memperkenalkan extension function yang seolah-olah menambahkan method ke class yang sudah ada, tanpa menyentuh kode sumber class tersebut:

kotlin Kotlin: extension function tanpa subclass
fun String.berawalanHurufVokal(): Boolean =
    this.isNotEmpty() && this.first().lowercaseChar() in "aiueo"

Setelah dideklarasikan, function di atas bisa dipanggil seolah method built-in: "Andi".berawalanHurufVokal().

Smart Cast #

Di Java, setelah memeriksa instance dengan instanceof, kita tetap harus melakukan cast eksplisit. Kotlin melakukan smart cast otomatis di dalam cabang yang sudah diperiksa:

kotlin Kotlin: smart cast otomatis
fun panjangNilai(x: Any): Int {
    if (x is String) {
        return x.length   // otomatis dianggap String di cabang ini
    }
    return -1
}

When Expression #

switch di Java terbatas pada konstanta dan tidak mengembalikan nilai. Kotlin menggantinya dengan when yang merupakan expression — bisa mengembalikan nilai dan menerima kondisi yang lebih ekspresif:

kotlin Kotlin: when sebagai expression
fun deskripsi(n: Int): String = when {
    n == 0 -> "nol"
    n in 1..9 -> "satuan"
    n in 10..99 -> "puluhan"
    else -> "besar"
}

Coroutines vs Thread #

Java menggunakan thread OS untuk konkurensi berat, atau CompletableFuture yang relatif verbose. Kotlin memperkenalkan coroutines — concurrency ringan dengan syntax suspend yang terlihat seperti kode sequensial:

kotlin Kotlin: coroutine ringan dengan suspend
suspend fun ambilData(): String {
    delay(100)   // tidak memblok thread
    return "data"
}

Coroutine bisa berjumlah ratusan ribu dalam satu thread, sesuatu yang tidak praktis dengan thread OS.

Interoperabilitas Dua Arah #

Interoperabilitas Kotlin-Java bekerja dua arah tanpa wrapper:

  • Kode Kotlin bisa memanggil class Java langsung, termasuk library seperti Spring, JUnit, dan Jackson.
  • Kode Java bisa memanggil class Kotlin; nama property Kotlin bisa diakses sebagai getter/setter Java standar.

Hal ini memungkinkan migrasi bertahap: satu file Kotlin dalam proyek Java raksasa tidak akan merusak apa pun.

Contoh Terpadu #

Program berikut menggabungkan beberapa fitur Kotlin di atas — data class, extension function, when, dan smart cast — dalam satu file runnable:

kotlin
data class Pelanggan(val nama: String, val saldo: Long)

fun String.berawalanHurufVokal(): Boolean =
    this.isNotEmpty() && this.first().lowercaseChar() in "aiueo"

fun formatSaldo(pel: Pelanggan?): String {
    return when {
        pel == null -> "Pelanggan tidak ditemukan"
        pel.saldo < 0 -> "${pel.nama} menunggak Rp${-pel.saldo}"
        pel.saldo == 0L -> "${pel.nama} tidak punya saldo"
        else -> "${pel.nama} memiliki Rp${pel.saldo}"
    }
}

fun main() {
    val daftar = listOf(
        Pelanggan("Budi", 50_000),
        Pelanggan("Siti", 0),
        Pelanggan("Atmin", -25_000),
        null
    )
    daftar.forEach { p ->
        println(formatSaldo(p))
    }

    val nama = "Andi"
    println("Nama $nama berawalan huruf vokal? ${nama.berawalanHurufVokal()}")
}

Output yang Dihasilkan:

text
Budi memiliki Rp50000
Siti tidak punya saldo
Atmin menunggak Rp25000
Pelanggan tidak ditemukan
Nama Andi berawalan huruf vokal? true

Beberapa hal yang patut dicatat dari kode di atas:

  1. formatSaldo(pel: Pelanggan?): parameter bisa null, ditandai ?. Pemanggil wajib sadar kemungkinan tersebut.
  2. when { ... }: cabang dievaluasi berurutan; cabang pertama yang cocok menang. Cabang else wajib agar when return value lengkap.
  3. ${pel.nama}: setelah cabang pel == null dilewati, smart cast membuat pel non-null sehingga pel.nama aman diakses.
  4. "Andi".berawalanHurufVokal(): extension function dipanggil seolah method built-in String.

Referensi #

Referensi Lanjutan #

Selanjutnya #

Java adalah bahasa yang paling sering dibandingkan dengan Kotlin, tetapi Kotlin juga sering dipertimbangkan bersama Python, Swift, Scala, Go, dan TypeScript. Pada chapter Kotlin vs Bahasa Pemrograman Lain, kita akan membandingkan Kotlin dengan lima bahasa tersebut untuk membantu kita yang datang dari latar belakang non-JVM.