Modifier Visibilitas

Modifier public, private, protected, dan internal.

Daftar isi

Modifier visibilitas menentukan dari mana sebuah deklarasi boleh diakses, yaitu public, private, protected, dan internal. Kotlin menjadikan public sebagai visibilitas bawaan, sehingga deklarasi tanpa modifier dapat dipanggil dari mana pun. Topik ini membahas makna keempat modifier tersebut, contoh pemakaiannya yang dapat dikompilasi, serta pesan error yang muncul ketika aturan visibilitas dilanggar. Seluruh contoh dijalankan nyata dan pesan errornya ditampilkan sebagaimana dikeluarkan compiler.

Visibilitas bawaan public#

Deklarasi tanpa modifier apa pun bersifat public, sehingga kata kunci tersebut jarang ditulis secara eksplisit. Kelas, fungsi tingkat atas, dan properti public dapat diakses dari berkas mana pun di dalam proyek. Visibilitas ditetapkan per deklarasi, sehingga satu kelas boleh memuat anggota public dan anggota tersembunyi sekaligus tanpa mengunci seluruh kelasnya.

Buat berkas main.kt berikut, lalu jalankan dengan dua perintah di bawahnya.

kotlin main.kt
internal fun formatNomor(nomor: Int): String {
    return "KTU-$nomor"
}

class KartuUjian(val nomor: Int, private val pemilik: String) {
    private var nilaiTersimpan = 0

    var nilai: Int
        get() = nilaiTersimpan
        set(value) {
            if (value in 0..100) nilaiTersimpan = value
        }

    fun ringkasan(): String {
        return "Kartu ${formatNomor(nomor)} atas nama $pemilik bernilai $nilaiTersimpan"
    }
}

open class ArsipDinas {
    protected var jumlahBerkas = 0

    fun catat(jumlah: Int) {
        jumlahBerkas += jumlah
    }
}

class ArsipCabang : ArsipDinas() {
    fun laporan(): String {
        return "Berkas tercatat: $jumlahBerkas"
    }
}

fun main() {
    val kartu = KartuUjian(7, "Budi")
    kartu.nilai = 85
    println(kartu.ringkasan())

    val arsip = ArsipCabang()
    arsip.catat(12)
    println(arsip.laporan())
}
bash
# compiler Kotlin dan pustaka standar yang dipakai jalur ini
export PATH=/home/kal/my-project/tools/kotlinc/bin:/home/kal/my-project/tools/jdk-merged/bin:$PATH
export STDLIB=/home/kal/my-project/tools/kotlinc/lib/kotlin-stdlib.jar
# kompilasi berkas main.kt menjadi out.jar, lalu jalankan programnya
kotlinc main.kt -d out.jar && java -cp "out.jar:$STDLIB" MainKt
text Keluaran
Kartu KTU-7 atas nama Budi bernilai 85
Berkas tercatat: 12

Program di atas memuat keempat modifier dalam satu berkas yang sah untuk dikompilasi. Anggota nilai dan ringkasan bersifat public sehingga main bebas memanggilnya. Anggota pemilik dan nilaiTersimpan bersifat private dan hanya disentuh dari dalam KartuUjian sendiri, yaitu melalui ringkasan. Anggota jumlahBerkas bersifat protected dan dibaca oleh ArsipCabang yang merupakan turunan ArsipDinas. Fungsi formatNomor bersifat internal dan tetap dapat dipanggil karena seluruh berkas ini dikompilasi dalam satu modul. Relasi turunan pada ArsipCabang ditandai tanda titik dua setelah nama kelas, dan kata kunci open di depan ArsipDinas diperlukan agar kelas tersebut boleh diturunkan.

Menyembunyikan anggota dengan private#

Anggota private hanya dapat diakses dari dalam kelas yang mendeklarasikannya, sehingga modifier ini menjadi pilihan utama untuk menyembunyikan data internal. Pada KartuUjian, properti nilaiTersimpan disembunyikan dan digantikan oleh properti nilai yang setter-nya memeriksa rentang 0 sampai 100. Pola ini menjaga bahwa satu-satunya jalan memasukkan nilai adalah lewat pemeriksaan tersebut. Tanpa penyembunyian itu, kode luar dapat mengisi nilaiTersimpan dengan 999 tanpa dapat dicegah.

Pelanggaran terhadap private ditolak pada saat kompilasi. Berkas berikut sengaja memuat dua akses ilegal terhadap anggota private.

kotlin akseslahir.kt
class KartuUjian(val nomor: Int, private val pemilik: String) {
    private var nilaiTersimpan = 0
}

fun main() {
    val kartu = KartuUjian(7, "Budi")
    println(kartu.pemilik)
    println(kartu.nilaiTersimpan)
}
bash
# kompilasi berkas akseslahir.kt untuk melihat pesan error dari compiler
kotlinc akseslahir.kt -d akseslahir.jar
text Keluaran
akseslahir.kt:7:19: error: cannot access 'val pemilik: String': it is private in 'KartuUjian'.
    println(kartu.pemilik)
                  ^^^^^^^
akseslahir.kt:8:19: error: cannot access 'var nilaiTersimpan: Int': it is private in 'KartuUjian'.
    println(kartu.nilaiTersimpan)
                  ^^^^^^^^^^^^^^

Kedua pesan menyebut lokasi persisnya, yaitu anggota mana yang private dan di kelas mana anggota itu berada. Perbaikannya bukan membuka visibilitas anggotanya, melainkan menyediakan anggota public yang memuat logika pemberian akses, sebagaimana ringkasan pada program utama.

protected dan relasi turunan#

Anggota protected dapat diakses dari kelas yang mendeklarasikannya beserta seluruh turunannya, tetapi tetap tertutup bagi kode lain. Modifier ini selalu melekat pada relasi pewarisan, sehingga tidak bermakna pada kelas yang tidak dapat diturunkan. Pada main.kt, ArsipCabang membaca jumlahBerkas karena kelasnya menurunkan ArsipDinas, sedangkan fungsi catat tetap disediakan untuk kode luar yang bukan turunan.

Berkas berikut mencoba mengakses anggota protected dari luar relasi turunan dan gagal dikompilasi.

kotlin aksesperlindungan.kt
open class ArsipDinas {
    protected var jumlahBerkas = 0
}

fun main() {
    val arsip = ArsipDinas()
    arsip.jumlahBerkas = 5
    println(arsip.jumlahBerkas)
}
bash
# kompilasi berkas aksesperlindungan.kt untuk melihat pesan error dari compiler
kotlinc aksesperlindungan.kt -d aksesperlindungan.jar
text Keluaran
aksesperlindungan.kt:7:11: error: cannot access 'var jumlahBerkas: Int': it is protected in 'ArsipDinas'.
    arsip.jumlahBerkas = 5
          ^^^^^^^^^^^^
aksesperlindungan.kt:8:19: error: cannot access 'var jumlahBerkas: Int': it is protected in 'ArsipDinas'.
    println(arsip.jumlahBerkas)
                  ^^^^^^^^^^^^

Pesan errornya sama bentuknya dengan kasus private, tetapi menyebut kata protected sebagai penyebabnya. Perbaikannya juga serupa, yaitu memanggil fungsi public yang disediakan kelas induk, atau menurunkan kelas baru bila akses memang diperlukan di dalam konteks turunan.

internal dan batas modul#

Anggota internal dapat diakses dari mana pun di dalam satu modul kompilasi, dan modul adalah sekumpulan berkas yang dikompilasi bersama dalam satu perintah kotlinc. Modifier ini mengisi celah antara private yang terlalu ketat dan public yang terlalu terbuka, misalnya untuk fungsi bantu yang boleh dipakai seluruh proyek tetapi tidak boleh bocor keluar modul. Perilakunya paling jelas ketika dua berkas dikompilasi bersama, lalu dikompilasi terpisah.

Buat berkas pustaka.kt dan aplikasi.kt berikut di direktori yang sama.

kotlin pustaka.kt
internal fun nomorArsip(urutan: Int): String {
    return "ARS-$urutan"
}
kotlin aplikasi.kt
fun main() {
    println(nomorArsip(3))
}

Kompilasi kedua berkas dalam satu perintah membuatnya menjadi satu modul, sehingga panggilan nomorArsip sah dan program berjalan.

bash
# kedua berkas dikompilasi bersama sehingga membentuk satu modul
kotlinc pustaka.kt aplikasi.kt -d app.jar && java -cp "app.jar:$STDLIB" AplikasiKt
text Keluaran
ARS-3

Situasinya berubah ketika aplikasi.kt dikompilasi sendirian dengan pustaka.jar hanya sebagai pustaka. Kedua berkas kini tergolong modul yang berbeda, dan compiler menolak panggilan tersebut.

bash
# pustaka.jar dibuat dari pustaka.kt sebagai modul tersendiri
kotlinc pustaka.kt -d pustaka.jar
# aplikasi.kt dikompilasi sebagai modul lain dan menabrak batas internal
kotlinc aplikasi.kt -cp pustaka.jar -d aplikasi.jar
text Keluaran
aplikasi.kt:2:13: error: cannot access 'fun nomorArsip(urutan: Int): String': it is internal in file.
    println(nomorArsip(3))
            ^^^^^^^^^^

Pesan it is internal in file menegaskan bahwa anggota tersebut tidak pernah melewati batas modulnya. Pilihan perbaikannya konsisten dengan makna modifier, yaitu menjadikan fungsi itu public apabila memang dimaksudkan dipakai lintas modul, atau menyatukan kedua berkas dalam satu kompilasi apabila keduanya memang satu kesatuan.

Kesalahan umum#

Kesalahan pertama adalah menganggap protected berarti dapat diakses dari mana pun di dalam berkas yang sama. Aturan itu berlaku untuk Java dalam beberapa kasus, sedangkan Kotlin tetap menutup anggota protected bagi kode yang bukan turunannya, dan pesan error pada bagian sebelumnya menunjukkan penolakan tersebut. Kesalahan kedua adalah memperlakukan internal seakan-akan sama dengan public karena keduanya terasa identik pada proyek kecil. Perbedaannya baru terlihat ketika modul dipecah atau dipakai ulang oleh proyek lain, dan pada saat itulah anggota internal berhenti terlihat.

Kesalahan ketiga adalah menaruh modifier visibilitas pada tempat yang tidak menerimanya, misalnya protected pada fungsi tingkat atas. Fungsi tingkat atas tidak terikat pada kelas mana pun, sehingga protected tidak bermakna di sana dan compiler menolak deklarasinya. Untuk fungsi tingkat atas, pilihan yang tersedia adalah public, private, dan internal.

Ringkasan#

  • Deklarasi tanpa modifier bersifat public dan dapat diakses dari mana pun.
  • private membatasi akses pada kelas yang mendeklarasikan anggota tersebut, dan pelanggarannya ditolak compiler dengan pesan lokasi yang presisi.
  • protected membuka akses bagi turunan saja, sedangkan internal membuka akses bagi satu modul kompilasi.
  • Dua berkas yang dikompilasi bersama menjadi satu modul, sedangkan berkas yang dikompilasi terpisah tergolong modul yang berbeda.

Referensi#