Tipe koleksi Kotlin (List, MutableList, Map) adalah tipe alias untuk java.util.List dan java.util.Map di bytecode JVM. Hal ini membuat interoperabilitas koleksi antar dua bahasa menjadi nyaris transparan — ArrayList<Int> Java dapat dipass langsung ke parameter List<Int> Kotlin. Namun transparansi ini menyembunyikan perbedaan penting: List Kotlin bersifat read-only di level kompilator, sedangkan java.util.List Java tetap mutable. Bab ini membahas perbedaan tersebut, konversi eksplisit, dan jebakan platform type pada elemen koleksi.
Tiga Aturan Interoperabilitas Koleksi #
| Tipe Kotlin | Padanan Java | Mutability di Java |
|---|---|---|
List<T> (read-only) | java.util.List<T> | Tetap mutable! |
MutableList<T> | java.util.List<T> | Mutable (konsisten) |
Map<K, V> (read-only) | java.util.Map<K, V> | Tetap mutable! |
MutableMap<K, V> | java.util.Map<K, V> | Mutable (konsisten) |
Set<T> (read-only) | java.util.Set<T> | Tetap mutable! |
Aturan kunci: List Kotlin hanya bersifat read-only dari perspektif Kotlin. Di bytecode Java, method add(), remove(), set() tetap ada di class. Kode Java yang menerima List Kotlin tetap dapat memutasi koleksi — kompilator Kotlin tidak dapat mencegahnya.
Contoh Lengkap: Konversi, View, dan Platform Type #
Kode berikut mendemonstrasikan empat skenario: List read-only dipass ke function, MutableList dimutasi, konversi eksplisit toList()/toMutableList(), dan platform type pada elemen:
import java.util.ArrayList
import java.util.HashMap
// Demonstrasi interoperabilitas koleksi Java <-> Kotlin:
// - Kotlin List<T> (read-only) <-> java.util.List<T> (mutable di Java)
// - Kotlin MutableList<T> <-> java.util.List<T> (mutable)
// - Elemen dari koleksi Java adalah platform type T!
// - Konversi eksplisit: toList(), toMutableList(), asList()
// Function Kotlin yang menerima List read-only (secara Java: List yang bisa dimutasi)
fun hitungTotal(harga: List<Int>): Int {
return harga.sum()
}
// Function Kotlin yang menerima MutableList
fun tambahDiskon(harga: MutableList<Int>) {
for (i in harga.indices) {
harga[i] = harga[i] - (harga[i] / 10) // diskon 10%
}
}
// Function Kotlin yang menerima java.util.ArrayList eksplisit
fun prosesArrayListJava(daftar: ArrayList<String>): Int {
return daftar.size
}
fun main() {
// 1. List Kotlin read-only vs ArrayList Java
val listKotlin: List<Int> = listOf(100, 200, 300)
val arrJava: ArrayList<Int> = ArrayList()
arrJava.add(100)
arrJava.add(200)
arrJava.add(300)
println("=== List Kotlin vs ArrayList Java ===")
println("listKotlin : $listKotlin (${listKotlin::class.simpleName})")
println("arrJava : $arrJava (${arrJava::class.simpleName})")
// 2. List Kotlin dapat dipass ke function yang minta ArrayList? Tidak langsung.
// ArrayList Java dapat dipass ke function yang minta List Kotlin? Ya.
println()
println("=== ArrayList Java -> List Kotlin ===")
println("hitungTotal(arrJava) : ${hitungTotal(arrJava)}")
// arrJava adalah ArrayList yang IS-A List, jadi accepted.
// 3. MutableList Kotlin dapat dimodifikasi
println()
println("=== MutableList dimodifikasi ===")
val harga: MutableList<Int> = mutableListOf(1000, 2000, 3000)
println("sebelum : $harga")
tambahDiskon(harga)
println("setelah : $harga")
// 4. List read-only TIDAK dapat dipass ke MutableList parameter
// hitungTotal(listKotlin) // OK
// tambahDiskon(listKotlin) // COMPILE ERROR: List<Int> not MutableList<Int>
// 5. Konversi eksplisit: List <-> MutableList
println()
println("=== Konversi eksplisit ===")
val readOnly: List<Int> = listOf(1, 2, 3)
val mutable: MutableList<Int> = readOnly.toMutableList()
mutable.add(4)
println("readOnly : $readOnly")
println("mutable : $mutable")
val readOnlyBaru: List<Int> = mutable.toList()
println("backToRO : $readOnlyBaru")
// 6. Elemen dari koleksi Java adalah platform type
println()
println("=== Platform type dari koleksi Java ===")
val peta: HashMap<String, String?> = HashMap()
peta["a"] = "Budi"
peta["b"] = null
// elemen dari peta["a"] adalah platform type (bisa null)
for (k in listOf("a", "b", "c")) {
val nilai: String? = peta[k]
println("peta[$k] = ${nilai ?: "null (tidak ada)"}")
}
// 7. ArrayList Java mutable -> Kotlin view read-only via toList()
println()
println("=== toList(): snapshot read-only ===")
val arr: ArrayList<Int> = ArrayList()
arr.add(10); arr.add(20); arr.add(30)
val viewReadOnly: List<Int> = arr.toList() // snapshot, tidak bisa dimutasi
// arr masih bisa dimutasi, viewReadOnly tidak bisa
arr.add(40)
println("arr : $arr")
println("viewReadOnly : $viewReadOnly")
// viewReadOnly.add(50) // ERROR: List tidak punya add()
// 8. Tipe primitif vs boxed di koleksi Java
println()
println("=== Primitif vs boxed ===")
val ints: MutableList<Int> = mutableListOf(1, 2, 3)
println("ints class : ${ints::class}")
}Output yang Dihasilkan:
=== List Kotlin vs ArrayList Java ===
listKotlin : [100, 200, 300] (ArrayList)
arrJava : [100, 200, 300] (ArrayList)
=== ArrayList Java -> List Kotlin ===
hitungTotal(arrJava) : 600
=== MutableList dimodifikasi ===
sebelum : [1000, 2000, 3000]
setelah : [900, 1800, 2700]
=== Konversi eksplisit ===
readOnly : [1, 2, 3]
mutable : [1, 2, 3, 4]
backToRO : [1, 2, 3, 4]
=== Platform type dari koleksi Java ===
peta[a] = Budi
peta[b] = null (tidak ada)
peta[c] = null (tidak ada)
=== toList(): snapshot read-only ===
arr : [10, 20, 30, 40]
viewReadOnly : [10, 20, 30]
=== Primitif vs boxed ===
ints class : class java.util.ArrayListMari kita telaah baris-baris kode di atas:
- Baris 17 (
fun hitungTotal(harga: List<Int>)): parameter bertipeList<Int>read-only di Kotlin. Secara bytecode,List<Int>Kotlin adalahjava.util.List<Integer>Java. Methodadd(),remove(),set()tidak dipanggil dari dalam function — kompilator Kotlin melarangnya. Namun kode Java yang memanggil function ini tetap dapat memutasi list sebelumnya. - Baris 23 (
fun tambahDiskon(harga: MutableList<Int>)): parameterMutableList<Int>eksplisit mengizinkan mutasi. Sisi Java yang memanggil function ini harus menyadari bahwa list yang diberikan akan dimutasi. Kontrak ini dibuat eksplisit di tipe parameter, bukan di dokumentasi. - Baris 41-42 (
listKotlin::class.simpleNamemenghasilkan “ArrayList”):listOf(100, 200, 300)di bytecode JVM menghasilkan instancejava.util.ArrayList— tipe konkret sama denganArrayListJava. PerbedaanListdanMutableListhanya di level tipe Kotlin, bukan di runtime. - Baris 51 (
hitungTotal(arrJava)):ArrayList<Int>Java adalahList<Int>(IS-A), sehingga dapat dipass ke parameterList<Int>Kotlin. Tidak ada konversi atau copy. Hal yang sama berlaku sebaliknya:MutableList<Int>Kotlin dapat dipass ke method Java yang menerimajava.util.List<Integer>. - Baris 68 (
readOnly.toMutableList()): konversi eksplisit membuat salinan mutable dari list read-only. Modifikasi padamutabletidak memengaruhireadOnlykarena data disalin. Operasi ini O(n). - Baris 76 (
peta["a"]mengembalikan platform type): elemen dariHashMap<String, String?>adalahString?di Kotlin karena tipe generikVadalahString?. Bila tipe generik Java adalahVtanpa nullability annotation, elemennya menjadi platform typeString!. Inilah jebakan klasik: kode Kotlin yang mengasumsikan elemen non-null dapat melempar NPE bila koleksi Java ternyata berisi null. - Baris 84 (
arr.toList()membuat snapshot):toList()menyalin elemen ke list baru. Modifikasi padaarrsetelahnya tidak memengaruhiviewReadOnly. Ini berbeda dariasList()Java yang membuat view hidup (live view). Kotlin sengaja memakai salinan untuk menjaga immutability. - Baris 99 (
ints::classmenghasilkanclass java.util.ArrayList): bukti konkret bahwamutableListOf(1, 2, 3)di bytecode JVM adalah instancejava.util.ArrayList. Tidak ada wrapper Kotlin di runtime — semuanya tipe Java standar.
Strategi Konversi dan Salinan #
| Operasi | Implementasi | Kompleksitas | Dampak pada Asal |
|---|---|---|---|
list.toList() | Salin elemen ke List baru | O(n) | Tidak ada (asal tak tersentuh) |
list.toMutableList() | Salin elemen ke MutableList baru | O(n) | Tidak ada |
list.toSet() | Salin elemen ke Set baru | O(n) | Tidak ada |
list.toCollection(dest) | Tambah elemen ke koleksi tujuan | O(n) | Tujuan dimutasi |
mutableList.toList() | Salinan read-only | O(n) | Tidak ada |
Referensi Lanjutan #
- Calling Java from Kotlin — dokumentasi Java interop Kotlin terkait koleksi mutable dan read-only
Selanjutnya #
Bab penutup section ini merangkum praktik terbaik interoperabilitas: anotasi nullability di Java, @JvmStatic untuk companion method, deklarasi tipe eksplisit untuk platform type, dan strategi pengujian dari kedua sisi. Bab Interoperability Best Practices akan menyatukan semuanya.