Array dan List sering dipakai bergantian untuk menyimpan sekumpulan nilai, tetapi keduanya punya karakteristik yang berbeda. Array berukuran tetap dengan elemen yang bisa diganti; List dapat bersifat read-only atau mutable. Pada chapter ini kita akan membandingkan keduanya secara langsung, mulai dari kemampuan mutasi, operasi yang tersedia, hingga rekomendasi kapan memilih satu di antaranya.

Tabel Perbandingan #

Berikut ringkasan perbedaan utama:

AspekArray<T>List<T>MutableList<T>
UkuranTetap, tidak bisa berubahTetap (read-only)Bisa berubah
ElemenBisa diganti melalui [i] = xTidak bisa digantiBisa diganti
add / removeTidak adaTidak adaAda
Operasi stdlibDasarSangat banyakSangat banyak
Tipe elemenSelalu objek (boxed)Bebas (atau primitif via IntArray)Bebas
Interop JavaT[]java.util.Listjava.util.List
Konstruktor defaultarrayOf, Array(n) { ... }listOf, emptyList()mutableListOf

Karakteristik Mutasi #

Array hanya mengizinkan penggantian nilai pada indeks yang sudah ada — ukuran tidak bisa berubah. MutableList mengizinkan penambahan dan penghapusan elemen sehingga ukuran bisa tumbuh atau menyusut.

kotlin Contoh: perbedaan mutasi
val arr = arrayOf(1, 2, 3)
arr[0] = 100        // diperbolehkan
// arr.add(4)       // tidak ada metode add

val mlist = mutableListOf(1, 2, 3)
mlist[0] = 100      // diperbolehkan
mlist.add(4)        // diperbolehkan: ukuran menjadi 4

List<T> (read-only) berdiri di tengah: ia tidak mengizinkan penggantian elemen maupun perubahan ukuran melalui API publik. Karena itu, List adalah pilihan terbaik untuk parameter fungsi dan properti publik yang tidak boleh dimutasi oleh pemanggil.

Operasi yang Tersedia #

Keduanya mendukung iterasi dan operasi fungsional, tetapi List memiliki lebih banyak operasi bawaan pustaka standar: map, filter, fold, sorted, chunked, windowed, groupBy, dan ratusan lainnya. Array<T> mendukung sebagian besar operasi yang sama melalui extension function, tetapi selalu mengembalikan List<T>:

kotlin Contoh: operasi pada Array
val arr = arrayOf(1, 2, 3)
val hasil = arr.map { it * 2 }   // List<Int>, bukan Array<Int>
val filtered = arr.filter { it > 1 }  // List<Int>

Pada List, hasil operasi tetap List, sehingga komposisi operasi tetap dalam ranah yang sama.

Contoh Lengkap #

kotlin
fun main() {
    // === Perbandingan ukuran ===
    val array = arrayOf(1, 2, 3)
    val list = listOf(1, 2, 3)
    val mutableList = mutableListOf(1, 2, 3)

    println("Array size: ${array.size}, List size: ${list.size}")

    // === Akses indeks (sama) ===
    println("array[0]: ${array[0]}, list[0]: ${list[0]}")

    // === Mutasi elemen ===
    array[0] = 100
    mutableList[0] = 100
    // list[0] = 100  // compile error: List read-only
    println("Array setelah modifikasi: ${array.toList()}")
    println("MutableList setelah modifikasi: $mutableList")

    // === Ukuran bisa berubah hanya pada MutableList ===
    mutableList.add(4)
    println("MutableList setelah add: $mutableList")
    // array.add(4)            // tidak ada metode add
    // list.add(4)             // tidak bisa

    // === Operasi bawaan ===
    println("List.map: ${list.map { it * 2 }}")
    println("List.filter: ${list.filter { it > 1 }}")
    println("Array.map: ${array.map { it * 2 }}")
    println("Array.toList(): ${array.toList()}")

    // === Konversi dua arah ===
    val arrayKeList: List<Int> = array.toList()
    val listKeArray: Array<Int> = list.toTypedArray()
    println("Array -> List: $arrayKeList")
    println("List -> Array: ${listKeArray.toList()}")

    // === Interop dengan varargs Java ===
    fun jumlah(vararg angka: Int): Int = angka.sum()
    val denganArray = jumlah(*array.toIntArray())  // spread operator
    println("Hasil varargs: $denganArray")

    // === Iterasi identik ===
    for (x in array) print("$x ")
    println()
    for (x in list) print("$x ")
    println()

    // === Perbedaan tipe runtime ===
    println("Runtime type array: ${array::class.simpleName}")
    println("Runtime type list: ${list::class.simpleName}")
    println("Runtime type mutableList: ${mutableList::class.simpleName}")

    // === Perbandingan fitur (tabel ringkas dicetak) ===
    println("--- Perbandingan ---")
    println("Array        | size tetap | elemen mutable | add/remove: tidak")
    println("List         | size tetap | read-only      | add/remove: tidak")
    println("MutableList  | size ubah  | elemen mutable | add/remove: ya")
}

Output yang Dihasilkan:

text
Array size: 3, List size: 3
array[0]: 1, list[0]: 1
Array setelah modifikasi: [100, 2, 3]
MutableList setelah modifikasi: [100, 2, 3]
MutableList setelah add: [100, 2, 3, 4]
List.map: [2, 4, 6]
List.filter: [2, 3]
Array.map: [200, 4, 6]
Array.toList(): [100, 2, 3]
Array -> List: [100, 2, 3]
List -> Array: [1, 2, 3]
Hasil varargs: 105
100 2 3 
1 2 3 
Runtime type array: Array
Runtime type list: ArrayList
Runtime type mutableList: ArrayList
--- Perbandingan ---
Array        | size tetap | elemen mutable | add/remove: tidak
List         | size tetap | read-only      | add/remove: tidak
MutableList  | size ubah  | elemen mutable | add/remove: ya

Mari kita bedah baris-baris penting di atas:

  1. array[0] = 100: penggantian elemen Array bekerja karena elemen bersifat mutable. list[0] = 100 pada List akan ditolak kompilator.
  2. mutableList.add(4): menambah elemen di akhir, mengubah size dari 3 menjadi 4. Tidak ada padanan pada Array.
  3. array.map { it * 2 }: mengembalikan List<Int>, bukan Array<Int>. Untuk mendapatkan kembali Array, kita perlu memanggil .toTypedArray().
  4. array.toList() dan list.toTypedArray(): konversi dua arah. Konversi ini membuat salinan, bukan view, sehingga perubahan pada satu tidak memengaruhi yang lain.
  5. jumlah(*array.toIntArray()): spread operator * membongkar array menjadi argumen varargs. Untuk Array<Int>, kita harus mengonversi ke IntArray lebih dulu karena signature vararg angka: Int di JVM memerlukan int[].

Kapan Memilih Array atau List #

Rekomendasi praktis:

  • Default pilih List. API-nya kaya, tipe eksplisit (read-only vs mutable), dan tidak perlu pusing dengan boxing.
  • Pilih Array<T> ketika memanggil API Java yang menerima T[] atau varargs.
  • Pilih array primitif (IntArray, LongArray, dll.) untuk koleksi besar atau hot path.
  • Pilih MutableList ketika ukuran perlu berubah seiring waktu dan kita ingin tetap menggunakan API List di tempat lain.

Referensi Lanjutan #

Setelah memahami perbedaan struktur dasar collection, kita siap masuk ke ranah operasi fungsional yang menjadi kekuatan utama Kotlin: map, filter, forEach, dan kawan-kawannya akan dibahas pada chapter operasi collection.