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:
data class Pengguna(val nama: String, val umur: Int)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:
val a: String = "halo" // tidak bisa null
val b: String? = null // boleh null
// a = null // galat kompilasiKompilator 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:
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:
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:
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:
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:
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:
Budi memiliki Rp50000
Siti tidak punya saldo
Atmin menunggak Rp25000
Pelanggan tidak ditemukan
Nama Andi berawalan huruf vokal? trueBeberapa hal yang patut dicatat dari kode di atas:
formatSaldo(pel: Pelanggan?): parameter bisanull, ditandai?. Pemanggil wajib sadar kemungkinan tersebut.when { ... }: cabang dievaluasi berurutan; cabang pertama yang cocok menang. Cabangelsewajib agarwhenreturn value lengkap.${pel.nama}: setelah cabangpel == nulldilewati, smart cast membuatpelnon-null sehinggapel.namaaman diakses."Andi".berawalanHurufVokal(): extension function dipanggil seolah method built-inString.
Referensi #
Referensi Lanjutan #
- Kotlin documentation home — halaman utama dokumentasi Kotlin untuk membandingkan fitur dengan Java
- Kotlin server-side overview — ringkasan Kotlin untuk backend sebagai konteks interoperabilitas dengan kode Java
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.