Mempelajari bahasa pemrograman Kotlin

Kotlin adalah bahasa pemrograman yang banyak digunakan oleh developer Android di mana saja. Topik ini berfungsi sebagai kursus kilat Kotlin yang dapat membantu Anda bekerja dengan cepat.

Deklarasi variabel

Kotlin menggunakan dua kata kunci yang berbeda untuk mendeklarasikan variabel: val dan var.

  • Gunakan val untuk variabel yang nilainya tidak pernah berubah. Anda tidak dapat menetapkan ulang nilai ke variabel yang dideklarasikan menggunakan val.
  • Gunakan var untuk variabel yang nilainya dapat berubah.

Pada contoh di bawah, count adalah variabel dari jenis Int yang diberi nilai awal 10:

var count: Int = 10

Int adalah jenis yang merepresentasikan bilangan bulat, salah satu dari berbagai jenis numerik yang dapat direpresentasikan di Kotlin. Serupa dengan bahasa lain, Anda juga dapat menggunakan Byte, Short, Long, Float, dan Double, bergantung pada data numerik Anda.

Kata kunci var berarti Anda dapat menetapkan ulang nilai ke count sesuai kebutuhan. Misalnya, Anda dapat mengubah nilai count dari 10 menjadi 15:

var count: Int = 10
count = 15

Namun, beberapa nilai tidak dapat diubah. Pertimbangkan String yang disebut languageName. Jika ingin memastikan bahwa languageName selalu memiliki nilai "Kotlin", Anda dapat mendeklarasikan languageName menggunakan kata kunci val:

val languageName: String = "Kotlin"

Kata kunci ini memungkinkan Anda untuk menjelaskan apa saja yang dapat diubah. Gunakan untuk keuntungan Anda sesuai kebutuhan. Jika referensi variabel harus ditetapkan ulang, deklarasikan sebagai var. Jika tidak, gunakan val.

Inferensi jenis

Melanjutkan contoh sebelumnya, saat Anda menetapkan nilai awal ke languageName, compiler Kotlin dapat menganggap jenis berdasarkan jenis nilai yang ditetapkan.

Karena nilai "Kotlin" adalah dari jenis String, compiler menganggap bahwa languageName juga merupakan String. Perhatikan bahwa Kotlin adalah bahasa berjenis statis. Ini berarti bahwa jenis ini diselesaikan pada waktu kompilasi dan tidak pernah berubah.

Pada contoh berikut, languageName dianggap sebagai String, sehingga Anda tidak dapat memanggil fungsi apa pun yang bukan bagian dari class String:

val languageName = "Kotlin"
val upperCaseName = languageName.toUpperCase()

// Fails to compile
languageName.inc()

toUpperCase() adalah fungsi yang hanya dapat dipanggil pada variabel jenis String. Karena compiler Kotlin telah menganggap languageName sebagai String, Anda dapat memanggil toUpperCase() dengan aman. Akan tetapi, inc() adalah fungsi operator Int, sehingga tidak dapat dipanggil di String. Pendekatan Kotlin untuk inferensi jenis memberi Anda keringkasan dan keamanan jenis.

Keamanan null

Dalam beberapa bahasa, variabel jenis referensi dapat dideklarasikan tanpa memberikan nilai eksplisit awal. Dalam kasus ini, variabel biasanya berisi nilai null. Secara default, variabel Kotlin tidak dapat memiliki nilai null. Artinya, cuplikan berikut tidak valid:

// Fails to compile
val languageName: String = null

Untuk variabel yang memiliki nilai null, jenisnya harus nullable. Anda dapat menentukan variabel sebagai nullable dengan menambahkan akhiran jenisnya dengan ?, seperti yang ditunjukkan dalam contoh berikut:

val languageName: String? = null

Dengan jenis String?, Anda dapat menetapkan nilai String atau null ke languageName.

Anda harus menangani variabel nullable dengan hati-hati atau akan timbul risiko NullPointerException yang dikhawatirkan. Di Java, misalnya, jika Anda mencoba memanggil metode pada nilai null, program akan error.

Kotlin menyediakan sejumlah mekanisme untuk bekerja secara aman dengan variabel nullable. Untuk informasi selengkapnya, lihat Pola Kotlin umum di Android: Nullability.

Bersyarat

Kotlin memiliki beberapa mekanisme untuk menerapkan logika bersyarat. Yang paling umum adalah pernyataan if-else. Jika ekspresi yang digabungkan dalam tanda kurung di samping kata kunci if mengevaluasi ke true, kode dalam cabang tersebut (yaitu, kode yang langsung diikuti dan digabungkan dengan tanda kurung kurawal) akan dieksekusi. Jika tidak, kode dalam cabang else yang akan dieksekusi.

if (count == 42) {
    println("I have the answer.")
} else {
    println("The answer eludes me.")
}

Anda dapat merepresentasikan beberapa kondisi menggunakan else if. Hal ini memungkinkan Anda merepresentasikan logika yang lebih terperinci dan kompleks dalam pernyataan bersyarat tunggal, seperti yang ditunjukkan dalam contoh berikut:

if (count == 42) {
    println("I have the answer.")
} else if (count > 35) {
    println("The answer is close.")
} else {
    println("The answer eludes me.")
}

Pernyataan bersyarat berguna untuk merepresentasikan logika berstatus, tetapi Anda mungkin mendapati bahwa Anda melakukan pengulangan sendiri saat menulisnya. Pada contoh di atas, Anda cukup mencetak String di setiap cabang. Untuk menghindari pengulangan ini, Kotlin menawarkan ekspresi bersyarat. Contoh terakhir dapat ditulis ulang sebagai berikut:

val answerString: String = if (count == 42) {
    "I have the answer."
} else if (count > 35) {
    "The answer is close."
} else {
    "The answer eludes me."
}

println(answerString)

Secara implisit, setiap cabang bersyarat menampilkan hasil ekspresi pada baris terakhirnya, sehingga Anda tidak perlu menggunakan kata kunci return. Karena hasil dari ketiga cabang berjenis String, hasil dari ekspresi if-else juga berjenis String. Dalam contoh ini, answerString diberi nilai awal dari hasil ekspresi if-else. Inferensi jenis dapat digunakan untuk menghapus deklarasi jenis eksplisit untuk answerString, tetapi sebaiknya sertakan deklarasi tersebut untuk kejelasan.

Dengan semakin kompleksnya pernyataan bersyarat, Anda dapat mempertimbangkan untuk mengganti ekspresi if-else dengan ekspresi when, seperti yang ditunjukkan dalam contoh berikut:

val answerString = when {
    count == 42 -> "I have the answer."
    count > 35 -> "The answer is close."
    else -> "The answer eludes me."
}

println(answerString)

Setiap cabang dalam ekspresi when direpresentasikan oleh suatu kondisi, tanda panah (->), dan hasilnya. Jika kondisi di sisi kiri tanda panah mengevaluasi ke true, maka hasil ekspresi di sisi kanan akan ditampilkan. Perhatikan bahwa eksekusi tidak jatuh dari satu cabang ke cabang berikutnya. Kode dalam contoh ekspresi when secara fungsional setara dengan contoh ekspresi dalam contoh sebelumnya, tetapi boleh dibilang lebih mudah dibaca.

Persyaratan Kotlin menyoroti salah satu fiturnya yang lebih canggih, yaitu transmisi cerdas. Daripada menggunakan operator panggilan aman atau operator pernyataan not-null agar berfungsi dengan nilai nullable, Anda dapat memeriksa apakah sebuah variabel berisi referensi ke nilai null menggunakan pernyataan bersyarat, seperti yang ditunjukkan dalam contoh berikut:

val languageName: String? = null
if (languageName != null) {
    // No need to write languageName?.toUpperCase()
    println(languageName.toUpperCase())
}

Dalam cabang bersyarat, languageName dapat dianggap sebagai non-nullable. Kotlin cukup cerdas untuk mengetahui bahwa kondisi untuk menjalankan cabang adalah karena languageName tidak berisi nilai null, sehingga Anda tidak perlu memperlakukan languageName sebagai nullable dalam cabang tersebut. Transmisi cerdas ini berfungsi untuk pemeriksaan null, pemeriksaan jenis, atau kondisi apa pun yang memenuhi kontrak.

Fungsi

Anda dapat mengelompokkan satu atau beberapa ekspresi menjadi fungsi. Daripada mengulangi rangkaian ekspresi yang sama setiap kali membutuhkan hasil, Anda dapat menggabungkan ekspresi dalam fungsi dan memanggil fungsi tersebut.

Untuk mendeklarasikan fungsi, gunakan kata kunci fun diikuti dengan nama fungsi. Selanjutnya, tentukan jenis input yang digunakan oleh fungsi Anda, jika ada, dan deklarasikan jenis output yang dihasilkannya. Isi fungsi adalah tempat Anda menetapkan ekspresi yang dipanggil saat fungsi Anda dipanggil.

Berdasarkan contoh sebelumnya, berikut adalah fungsi Kotlin yang lengkap:

fun generateAnswerString(): String {
    val answerString = if (count == 42) {
        "I have the answer."
    } else {
        "The answer eludes me"
    }

    return answerString
}

Fungsi dalam contoh di atas memiliki nama generateAnswerString. Tidak perlu input apa pun. Ini menghasilkan hasil dari jenis String. Untuk memanggil fungsi, gunakan namanya, diikuti oleh operator panggilan (()). Pada contoh di bawah, variabel answerString diinisialisasi dengan hasil dari generateAnswerString().

val answerString = generateAnswerString()

Fungsi dapat mengambil argumen sebagai input, seperti yang ditunjukkan pada contoh berikut:

fun generateAnswerString(countThreshold: Int): String {
    val answerString = if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }

    return answerString
}

Saat mendeklarasikan fungsi, Anda dapat menentukan berapa pun jumlah argumen dan jenisnya. Pada contoh di atas, generateAnswerString() mengambil satu argumen yang bernama countThreshold dari jenis Int. Dalam fungsi, Anda dapat merujuk ke argumen menggunakan namanya.

Saat memanggil fungsi ini, Anda harus menyertakan argumen dalam tanda kurung panggilan fungsi:

val answerString = generateAnswerString(42)

Menyederhanakan deklarasi fungsi

generateAnswerString() adalah fungsi yang cukup sederhana. Fungsi tersebut mendeklarasikan variabel, dan langsung menampilkan hasil. Jika hasil ekspresi tunggal ditampilkan dari fungsi, Anda dapat melewati deklarasi variabel lokal dengan langsung menampilkan hasil ekspresi if-else yang terdapat dalam fungsi, seperti yang ditunjukkan dalam contoh berikut:

fun generateAnswerString(countThreshold: Int): String {
    return if (count > countThreshold) {
        "I have the answer."
    } else {
        "The answer eludes me."
    }
}

Anda juga dapat mengganti kata kunci kembali dengan operator penugasan:

fun generateAnswerString(countThreshold: Int): String = if (count > countThreshold) {
        "I have the answer"
    } else {
        "The answer eludes me"
    }

Fungsi anonim

Tidak semua fungsi memerlukan nama. Beberapa fungsi lebih teridentifikasi secara langsung berdasarkan input dan outputnya. Fungsi ini disebut fungsi anonim. Anda dapat menyimpan referensi ke fungsi anonim, menggunakan referensi ini untuk memanggil fungsi anonim nanti. Anda juga dapat meneruskan referensi di sekitar aplikasi, seperti jenis referensi lainnya.

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

Seperti fungsi bernama, fungsi anonim dapat berisi sejumlah ekspresi. Nilai dari fungsi yang ditampilkan adalah hasil ekspresi akhir.

Pada contoh di atas, stringLengthFunc berisi referensi ke fungsi anonim yang mengambil file String sebagai input dan menampilkan panjang input String sebagai output dari jenis Int. Karena itu, jenis fungsi dinyatakan sebagai (String) -> Int. Akan tetapi, kode ini tidak menjalankan fungsinya. Untuk mengambil hasil fungsi, Anda harus memanggilnya seperti fungsi bernama. Anda harus memberikan String saat memanggil stringLengthFunc, seperti yang ditunjukkan d alam contoh berikut:

val stringLengthFunc: (String) -> Int = { input ->
    input.length
}

val stringLength: Int = stringLengthFunc("Android")

Fungsi urutan yang lebih tinggi

Fungsi dapat mengambil fungsi lain sebagai argumen. Fungsi yang menggunakan fungsi lain sebagai argumen disebut fungsi urutan yang lebih tinggi. Pola ini berguna untuk melakukan komunikasi antar komponen dengan cara yang sama seperti saat Anda menggunakan antarmuka callback di Java.

Berikut adalah contoh fungsi urutan yang lebih tinggi:

fun stringMapper(str: String, mapper: (String) -> Int): Int {
    // Invoke function
    return mapper(str)
}

Fungsi stringMapper() mengambil String bersama dengan fungsi yang memperoleh nilai Int dari String yang Anda teruskan ke dalamnya.

Anda dapat memanggil stringMapper() dengan meneruskan String dan fungsi yang memenuhi parameter input lainnya, yaitu fungsi yang mengambil String sebagai input dan output Int, seperti yang ditunjukkan dalam contoh berikut:

stringMapper("Android", { input ->
    input.length
})

Jika fungsi anonim adalah parameter last yang didefinisikan pada fungsi, Anda dapat meneruskannya di luar tanda kurung yang digunakan untuk memanggil fungsi, seperti yang ditunjukkan dalam contoh berikut:

stringMapper("Android") { input ->
    input.length
}

Fungsi anonim dapat ditemukan di seluruh library standar Kotlin. Untuk informasi selengkapnya, lihat Fungsi Urutan yang Lebih Tinggi dan Lambda.

Class

Semua jenis yang disebutkan sejauh ini di-build ke dalam bahasa pemrograman Kotlin. Jika ingin menambahkan jenis kustom Anda sendiri, tentukan class menggunakan kata kunci class, seperti yang ditunjukkan dalam contoh berikut:

class Car

Properti

Class merepresentasikan status menggunakan properti. Properti adalah variabel tingkat class yang dapat menyertakan pengambil, penyetel, dan kolom dukungan. Karena mobil memerlukan roda untuk berjalan, Anda dapat menambahkan daftar objek Wheel sebagai properti Car, seperti yang ditunjukkan dalam contoh berikut:

class Car {
    val wheels = listOf<Wheel>()
}

Perhatikan bahwa wheels adalah public val, artinya wheels dapat diakses dari luar class Car, dan tidak dapat ditetapkan ulang. Jika ingin mendapatkan instance dari Car, Anda harus terlebih dahulu memanggil konstruktornya. Dari sana, Anda dapat mengakses semua properti yang dapat diakses.

val car = Car() // construct a Car
val wheels = car.wheels // retrieve the wheels value from the Car

Jika ingin menyesuaikan roda, Anda dapat menentukan konstruktor kustom yang menentukan cara properti class Anda diinisialisasi:

class Car(val wheels: List<Wheel>)

Pada contoh di atas, konstruktor class mengambil List<Wheel> sebagai argumen konstruktor dan menggunakan argumen tersebut untuk menginisialisasi properti wheels miliknya.

Fungsi dan enkapsulasi class

Class menggunakan fungsi untuk memodelkan perilaku. Fungsi dapat mengubah status, sehingga membantu Anda menampilkan data yang hanya ingin Anda ungkap. Kontrol akses ini adalah bagian dari konsep berorientasi objek yang lebih besar yang dikenal sebagai enkapsulasi.

Pada contoh berikut, properti doorLock bersifat pribadi dari apa pun di luar class Car. Untuk membuka kunci mobil, Anda harus memanggil fungsi unlockDoor() yang meneruskan dengan kunci yang valid, seperti yang ditunjukkan dalam contoh berikut:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

Jika Anda ingin menyesuaikan cara properti direferensikan, Anda dapat memberikan pengambil dan penyetel kustom. Misalnya, jika Anda ingin menampilkan pengambil properti sekaligus membatasi akses ke penyetelnya, Anda dapat menetapkan penyetel tersebut sebagai private:

class Car(val wheels: List<Wheel>) {

    private val doorLock: DoorLock = ...

    var gallonsOfFuelInTank: Int = 15
        private set

    fun unlockDoor(key: Key): Boolean {
        // Return true if key is valid for door lock, false otherwise
    }
}

Dengan kombinasi properti dan fungsi, Anda dapat membuat class yang memodelkan semua jenis objek.

Interoperabilitas

Salah satu fitur terpenting Kotlin adalah interoperabilitas yang dapat disesuaikannya dengan Java. Karena kode Kotlin mengompilasi ke JVM bytecode, kode Kotlin Anda dapat memanggil langsung ke kode Java dan sebaliknya. Ini berarti Anda dapat memanfaatkan library Java yang sudah ada langsung dari Kotlin. Selain itu, sebagian besar Android API juga ditulis dalam Java, dan Anda dapat memanggilnya langsung dari Kotlin.

Langkah berikutnya

Kotlin adalah bahasa yang fleksibel dan pragmatis dengan dukungan dan momentum yang terus berkembang. Jika Anda belum mencobanya, lakukan sekarang. Untuk langkah selanjutnya, lihat dokumentasi Kotlin resmi beserta panduan tentang cara menerapkan pola Kotlin umum di aplikasi Android Anda.