Anda di halaman 1dari 200

Hello Kotlin!

Sebelum ke topik-topik fundamental tersebut, mari kita awali dengan pembahasan tentang
program Hello Kotlin!. Pada modul sebelumnya kita telah berjumpa dengan sebuah proyek
yang menampilkan sebuah teks Hello World! dan juga Hello Kotlin!.

Buat Anda yang pernah belajar pemrograman sebelumnya, tentu tak asing dengan program
ini. Hello World! sering digunakan untuk menunjukkan sintaks dasar pada sebuah bahasa
pemrograman. Karena kita sedang belajar bahasa pemrograman Kotlin, maka kita
mengganti namanya dengan Hello Kotlin!.

Hello Kotlin! merupakan sebuah program sederhana yang digunakan untuk mencetak


sebuah teks “Hello Kotlin!” ke dalam layar atau konsol. Berikut adalah contoh kode dari
program tersebut:

1. // main function
2. fun main() {
3.    println("Hello Kotlin!")
4. }

Baris pertama dari kode di atas adalah komentar yang ditandai dengan tanda //.

1. // main function

Sebuah komentar akan dilewatkan ketika proses kompilasi, sehingga tidak akan
mempengaruhi alur program yang kita tulis. Komentar bisa kita gunakan untuk
mendokumentasikan kode yang kita tulis agar ketika suatu saat kita membukanya kembali,
kita bisa mengetahui fungsi dari kode yang kita beri komentar tersebut.

Terdapat dua jenis komentar yang bisa kita gunakan. Pertama adalah single line
comment yaitu komentar satu baris yang diawali dengan tanda // dan berakhir di akhir baris
komentar tersebut.

1. // single line comment

Yang kedua adalah multi-line comment yang diawali dengan tanda /* dan diakhiri dengan


tanda */.

1. /*
2.    multi line comment
3.    Hello Kotlin
4. */

Dengan multi-line comment kita bisa menuliskan beberapa baris komentar.

Selanjutnya adalah fungsi yang bernama main(), fungsi yang wajib kita definisikan ketika
membuat sebuah program. Fungsi main() merupakan sebuah entry point yang  otomatis
akan dipanggil ketika program dijalankan. Pada modul berikutnya kita akan belajar lebih
dalam tentang bagaimana mendefinisikan sebuah fungsi.

Kemudian fungsi println(), fungsi yang akan kita gunakan untuk mencetak teks ke dalam
layar atau konsol. Fungsi println() membutuhkan satu argumen berupa message dengan tipe
data yang dikehendaki. Tipe data yang didukung untuk kita masukkan ke dalam
fungsi println() ada di tautan ini.
Selain fungsi println(), terdapat juga fungsi print() yang berfungsi sama seperti
fungsi println(). Bedanya, println() akan menambahkan baris baru setelah selesai mencetak
argumen yang diberikan, sementara fungsi print() tidak melakukan apapun ketika argumen
yang diberikan, selesai dicetak. Untuk memahaminya lebih dalam, coba jalankan kode
berikut:

1. fun main() {
2.    val name = "Alfian"
3.  
4.    print("Hello my name is ")
5.    println(name)
6.    print(if (true) "Always true" else "Always false")
7. }
8.  
9. /*
10.    output:
11.        Hello my name is Alfian
12.        Always true
13. */

Fungsi println() dan print() secara internal memanggil fungsi system.out.print(message).

1. @kotlin.internal.InlineOnly
2. public actual inline fun print(message: Any?) {
3.     System.out.print(message)
4. }

Kegunaan utama dari fungsi system.out.print(message) adalah untuk menampilkan pesan


yang diberikan ke standard output stream. Selain menampilkan pesan yang diberikan secara
eksplisit, fungsi tersebut juga dapat digunakan untuk menampilkan nilai dari
sebuah expression atau variabel seperti yang dicontohkan di atas.
Data Types & Variable
Data types atau tipe data adalah sebuah pengklasifikasian data berdasarkan jenis data
tersebut. Untuk mengembangkan sebuah program, ada beberapa tipe data yang akan kita
pelajari. Di antaranya adalah Character, String, Array, Numbers dan Booleans.
Semuanya akan kita bahas sejelas dan sesederhana mungkin di dalam modul ini.

Namun sebelumnya, ada satu hal yang kita perlu tahu terlebih dahulu, yaitu Variabel.
Umumnya variabel digunakan untuk menyimpan informasi atau nilai yang akan dikelola di
dalam sebuah program. Sebuah variabel akan membutuhkan kata
kunci var atau val, identifier, type dan initialization. Kira-kira strukturnya seperti berikut:

1. var identifier: Type = initialization

Berikut adalah contoh variabel dengan tipe String:

1. var company: String = "Dicoding"

Mari kita ulas setiap bagian pada struktur variabel di atas.

 var atau val
var atau val digunakan untuk mengontrol nilai dari sebuah variabel. Dengan kata
kunci var kita bisa mengubah nilai yang sudah kita inisialisasikan. Sebagai contoh:
1. var company: String = "Dicoding"
2. company = "Dicoding Academy"

Variabel company yang awalnya memiliki nilai “Dicoding” sekarang sudah diubah


menjadi “Dicoding Academy”. Sedangkan jika kita menggunakan kata kunci val,
kita tidak bisa mengubah nilai yang sebelumnya sudah kita inisialisasi. Jika kita
memaksa untuk mengubahnya, maka akan terjadi eror seperti berikut:

3. val company: String = "Dicoding"


4. company = "Dicoding Academy" //Val cannot be reassigned
 Identifier
Identifier merupakan nama dari sebuah variabel. Pada contoh kode di atas yang
merupakan identifier adalah company. Perlu diketahui bahwa di dalam sebuah
program kita tidak bisa membuat lebih dari 1 (satu) variabel dengan nama sama.
 Type
Pada bagian inilah kita menentukan tipe data dari variabel tersebut. Tipe data
dibutuhkan agar kompiler dapat mengetahui bagaimana sebuah data akan
digunakan. Tipe data dari contoh variabel di atas adalah String. Karena Kotlin
mendukung type inference maka kita diperbolehkan untuk tidak menuliskan tipe data
secara eksplisit:
1. val company = "Dicoding"
 Initialization

Dan yang terakhir adalah initialization atau nilai awal dari sebuah variabel. Pada
contoh di atas yang berperan
sebagai initialization adalah “Dicoding” dan “Dicoding Academy”.
Tipe data juga menentukan operasi apa saja yang dapat dilakukan pada sebuah variabel
dan bagaimana nilai dari sebuah variabel disimpan. Contoh, ketika kita menggunakan
operator + terhadap dua variabel yang bertipe String seperti berikut:

1. fun main() {
2.     val firstWord = "Dicoding "
3.     val lastWord = "Academy"
4.     print(firstWord + lastWord)
5. }
6. /*
7.    output: Dicoding Academy
8. */

Maka kedua nilai dari variabel firstWord dan lastWord akan digabungkan menjadi satu nilai.


Berbeda ketika kita menggunakan operator + pada variabel yang bertipe Int seperti berikut:

1. fun main() {
2.     val valueA: Int = 10
3.     val valueB = 20
4.     print(valueA + valueB)
5. }
6. /*
7.    output: 30
8. */

Kompiler akan menjalankan operasi aritmatika, seperti pada contoh di atas di mana nilai dari
variabel valueA dan valueB akan dijumlahkan lalu menghasilkan nilai baru.
Ketika kita mengembangkan sebuah program kita pasti membutuhkan variabel dengan tipe
data yang mampu menyimpan nilai berbentuk teks. Terdapat dua (2) tipe data yang bisa kita
gunakan, yaitu Char dan String.

Char
Characters direpresentasikan menggunakan tipe Char. Untuk mendefinisikan sebuah
variabel dengan tipe data Char kita bisa menggunakan tanda kutip tunggal (' ') seperti
berikut:

1. val character = 'A'

Tipe data Char hanya dapat kita gunakan untuk menyimpan karakter tunggal. Sebaliknya
jika kita memasukkan lebih dari 1 (satu) karakter, akan terjadi eror:

1. val character: Char = 'ABC' // Incorrect character literal

Yang menarik, kita bisa melakukan operasi increment (++) dan decrement (--) pada sebuah


variabel dengan tipe data Char seperti berikut:

1. fun main() {
2.     var vocal = 'A'
3.  
4.     println("Vocal " + vocal++)
5.     println("Vocal " + vocal++)
6.     println("Vocal " + vocal++)
7.     println("Vocal " + vocal--)
8.     println("Vocal " + vocal--)
9.     println("Vocal " + vocal--)
10.     println("Vocal " + vocal--)
11. }
12.  
13. /*
14.    output:
15.        Vocal A
16.        Vocal B
17.        Vocal C
18.        Vocal D
19.        Vocal C
20.        Vocal B
21.        Vocal A
22. */

Operasi increment dan decrement sendiri merupakan operasi yang bisa kita gunakan pada


tipe data Number. Lalu kenapa kita bisa menggunakannya pada tipe Char? Karena pada
dasarnya setiap Characters merupakan representasi dari Unicode. Contoh
Unicode A adalah 0041. Ketika kita melakukan increment maka hasilnya adalah 0042 yang
mana merupakan Unicode dari B.
String
String merupakan tipe data yang mirip dengan Char. Ia dapat digunakan untuk menyimpan
nilai berupa teks. Perbedaannya, String bisa menampung beberapa karakter di dalamnya.

String direpresentasikan menggunakan tipe String. Nilai yang berada di dalam sebuah
variabel dengan tipe data String merupakan kumpulan dari beberapa karakter. Kita bisa
mendefinisikan variabel tersebut dengan tanda petik ganda (" ") seperti berikut:

1. val textString  = "Kotlin"

Pada dasarnya sekumpulan karakter dalam String tersebut berbentuk Array, sehingga kita
bisa mendapatkan karakter tunggal dengan mudah. Caranya, manfaatkan indexing seperti
berikut:

1. fun main() {
2.     val text  = "Kotlin"
3.     val firstChar = text[0]
4.  
5.     print("First character of $text is $firstChar")
6. }
7.  
8. /*
9.   output : First character of Kotlin is K
10. */

Apa itu Indexing?


Indexing merupakan sebuah cara yang memudahkan kita untuk mengakses elemen yang berada di dalam sebuah
Collection dengan memanfaatkan index atau posisi dari elemen tersebut. Posisi dari sebuah elemen pada
umumnya dimulai dari angka 0. Untuk materi tentang Collection akan sama-sama kita pelajari pada modul
berikutnya. Semangat!

Nilai 0 yang berada pada indexing di atas adalah posisi karakter yang akan diakses. Selain
itu, kita juga dapat melakukan iterasi terhadap objek String dengan menggunakan for-
loop seperti berikut:

1. fun main() {
2.     val text  = "Kotlin"
3.     for (char in text){
4.         print("$char ")
5.     }
6. }
7.  
8. /*
9.   output : K o t l i n
10. */

Escaped String
Kotlin memiliki dua jenis tipe Literal String, yang pertama adalah Escaped String yang
memungkinkan kita untuk mengurangi ambiguitas nilai yang berada di dalam sebuah String.
Misalnya ketika kita mendefinisikan sebuah String berikut:

1. val statement = "Kotlin is Awesome!"


Kemudian kita ingin menambahkan tanda petik ganda di dalam sebuah String seperti
berikut:

1. val statement = "Kotlin is "Awesome!""

Maka akan terjadi ambiguitas nilai pada variabel statement karena kompiler tidak dapat
mengetahui akhir dari baris nilai untuk variabel statement. Untuk mengatasinya, kita bisa
melakukan escaped dengan menambahkan karakter backslash (\) sebelum tanda petik
ganda seperti berikut:

1. val statement = "Kotlin is \"Awesome!\""

Selain \” di atas, terdapat beberapa karakter lain yang dapat digunakan untuk
melakukan escaped di dalam sebuah String, antara lain:  

 \t: menambah tab ke dalam teks.


 \n: membuat baris baru di dalam teks.
 \’: menambah karakter single quote kedalam teks.
 \”: menambah karakter double quote kedalam teks.
 \\: menambah karakter backslash kedalam teks.

Selain itu, kita juga bisa menambahkan sebuah Unicode ke dalam sebuah String seperti
berikut:

1. fun main() {
2.     val name = "Unicode test: \u00A9"
3.     print(name)
4. }
5.  
6. /*
7.    output: Unicode test : ©
8. */

Raw String
Kedua, adalah Raw String yang memungkinkan kita menuliskan multiline dan arbitrary text.
Ketika ingin membuat beberapa baris String biasanya kita melakukan escaped terhadap
String dengan memanfaatkan karakter escape \n seperti berikut:

1. val line = "Line 1\n" +


2.         "Line 2\n" +
3.         "Line 3\n" +
4.         "Line 4\n"

Dengan Raw String, kita dapat membuatnya dengan cara yang lebih mudah yaitu seperti
berikut:

1. fun main() {
2.     val line = """
3.         Line 1
4.         Line 2
5.         Line 3
6.         Line 4
7.     """.trimIndent()
8.  
9.     print(line)
10. }
11.  
12. /*
13.     output:
14.         Line 1
15.         Line 2
16.         Line 3
17.         Line 4
18.  */

Pada kode di atas, kita mendefinisikan sebuah Raw String menggunakan triple quote ("""
"""). Raw String memungkinkan kita untuk membuat beberapa baris String tanpa
penggabungan (concatenation) dan penggunaan karakter escaped.
Functions
Function atau fungsi merupakan sebuah prosedur yang memiliki keterkaitan dengan pesan
dan objek. Ketika kita memanggil sebuah fungsi maka sebuah mini-program akan
dijalankan. Fungsi sendiri bisa diartikan sebagai cara sederhana untuk mengatur program
buatan kita.

Sebuah fungsi dapat kita gunakan untuk mengembalikan nilai. Pemanggilan sebuah fungsi
sendiri, bisa diberi argumen atau tidak. Pada modul ini kita akan belajar bagaimana
membuat sebuah fungsi pada Kotlin dan mencoba beberapa poin di atas.

1. fun functionName(param1: Type1, param2: Type2, ...): ReturnType {


2.     return result
3. }

Pendeklarasian fungsi pada Kotlin diawali dengan kata kunci fun kemudian dilanjutkan


dengan nama fungsi yang dikehendaki. Selanjutnya adalah parameter yang berada pada
fungsi yang dideklarasikan. Awali dengan nama parameter dan ikuti dengan tipe parameter
itu sendiri yang dipisahkan oleh karakter colon (:). Setiap parameter yang berada pada
sebuah fungsi dipisahkan oleh karakter koma dan berada di dalam tanda kurung. 

1. fun setUser(name: String, age: Int)

Setelah menentukan nama dan parameter, selanjutnya adalah menentukan tipe kembalian
dari fungsi yang dibuat. Perlu diketahui fungsi pada Kotlin selalu mengembalikan nilai. Tipe
kembalian adalah nilai yang akan dikembalikan ketika fungsi tersebut dipanggil.

1. fun setUser(name: String, age: Int): String

Fungsi di atas akan mengembalikan nilai berupa String. Setelah menentukan tipe nilai
kembalian, barulah kita menentukan function body di mana di dalamnya
terdapat expression atau statement untuk dijalankan. Function body berada di dalam curly
braces ({}) setelah tipe nilai kembalian.

1. fun setUser(name: String, age: Int): String {


2.     return "Your name is $name, and you $age years old"
3. }

Nilai yang akan dikembalikan diikuti oleh kata kunci return. Jika di dalam suatu fungsi hanya
memiliki satu expression untuk menentukan nilai kembalian, maka fungsi tersebut bisa
diubah menjadi expression body. Kita hanya perlu menambahkan tanda = dan
menuliskannya seperti berikut:

1. fun setUser(name: String, age: Int): String = "Your name is $name, and you $age years
old"

Dengan expression body, kompiler dapat menentukan tipe kembalian dari fungsi yang
dibuat. Sehingga kita tidak perlu menentukan tipe nilai kembalian secara eksplisit:

1. fun setUser(name: String, age: Int) = "Your name is $name, and you $age years old"

Jika kita tidak ingin fungsi yang dibuat mengembalikan nilai, kita bisa
menggunakan Unit sebagai tipe nilai kembaliannya. Contohnya seperti berikut:
1. fun printUser(name: String): Unit {
2.     print("Your name is $name")
3. }

Ketika menggunakan tipe kembalian Unit, Kotlin memungkinkan kita untuk


menghilangkannya. Kenapa demikian? Kompiler akan mendeteksinya sebagai tipe
kembalian yang redundant:

1. fun printUser(name: String) {


2.     print("Your name is $name")
3. }

Pemanggilan fungsi, bisa dilakukan dengan pendekatan tradisional seperti berikut:

1. fun main() {
2.     val user = setUser("Alfian", 19)
3.     println(user)
4.  
5.     printUser("Alfian")
6. }
7.  
8. fun setUser(name: String, age: Int) = "Your name is $name, and you $age years old"
9.  
10. fun printUser(name: String) {
11.     println("Your name is $name")
12. }
13.  
14. /*
15. output :
16. Your name is Alfian, and you 19 years old
17. Your name is Alfian
18. */
If Expressions
Saat mengembangkan sebuah program, kita pasti bertemu dengan alur program yang perlu
sebuah kondisi untuk menjalankan sebuah statement atau expression. Contoh ketika kita
ingin menginisialisasi nilai dari sebuah variabel berdasarkan suatu kondisi. Untuk
menyelesaikannya, gunakan If Expression.

If expression direpresentasikan dengan kata kunci if. If akan kita perlukan untuk


menyelesaikan kasus di atas, dimana if akan digunakan untuk menguji suatu kondisi untuk
menjalankan sebuah proses. If akan mengeksekusi sebuah statement atau expression jika
hasil evaluasi dari expressions yang diberikan pada blok if bernilai true. Sebaliknya, jika
bernilai false maka proses yang ditentukan akan dilewatkan.

1. val openHours = 7
2. val now = 20
3. if (now > openHours){
4.     println("office already open")
5. }

Kode di atas adalah contoh sederhana penggunaan if dengan memanfaatkan


operator greater than untuk membandingkan nilai. Jika if digunakan untuk mengembalikan
nilai atau menetapkan nilai dari sebuah variabel maka if wajib memiliki branch else.
Contohnya seperti berikut:

1. val openHours = 7
2. val now = 20
3. val office: String
4. if (now > openHours) {
5.     office = "Office already open"
6. } else {
7.     office = "Office is closed"
8. }
9.  
10. print(office)

Else akan dijalankan jika hasil evaluasi pada expression yang diberikan menghasilkan


nilai false. If merupakan sebuah expressions yang dapat mengembalikan nilai, sehingga kita
dapat menyimpan hasilnya ke dalam sebuah variabel.

1. val openHours = 7
2. val now = 20
3. val office: String
4. office = if (now > openHours) {
5.     "Office already open"
6. } else {
7.     "Office is closed"
8. }
9.  
10. print(office)

Pada kode di atas, kita hanya menggunakan If untuk menguji 2 (dua) kondisi. Lalu
bagaimana jika kita memiliki beberapa kondisi? Kita bisa menggabungkan else dan if seperti
berikut:

1. val openHours = 7
2. val now = 7
3. val office: String
4. office = if (now > 7) {
5.     "Office already open"
6. } else if (now == openHours){
7.     "Wait a minute, office will be open"
8. } else {
9.     "Office is closed"
10. }
11.  
12. print(office)

Blok else if akan dijalankan jika hasil evaluasi pada branch sebelumnya bernilai false. Jika


hasil evaluasi pada branch else if juga bernilai nilai false, maka lanjut ke
evaluasi branch selanjutnya.

Perlu diketahui bahwa Kotlin tidak mendukung ternary operator (condition ? then : else),


karena peran dari operator tersebut sudah digantikan dengan if expressions.
Pada modul sebelumnya kita telah belajar tentang If expressions yang
menggunakan Boolean expressions. Kini saatnya kita belajar apa itu Boolean? Boolean
adalah sebuah tipe data yang hanya memiliki dua nilai, yaitu true dan false. Terdapat 3
(tiga) operator yang dapat digunakan pada Boolean.

Conjunction atau AND (&&)


Operator AND (&&) akan mengembalikan nilai true jika semua hasil
evaluasi expression yang diberikan bernilai true.

1. fun main() {
2.     val officeOpen = 7
3.     val officeClosed = 16
4.     val now = 20
5.  
6.     val isOpen = if (now >= officeOpen && now <= officeClosed){
7.         true
8.     } else {
9.         false
10.     }
11.  
12.     print("Office is open : $isOpen")
13.  
14.     /*
15.         Output : Office is open : false
16.      */
17. }

Fungsi di atas menguji apakah jam sekarang berada di antara jam waktu buka kantor dan
jam tutup kantor. If expressions di atas bisa Anda sederhanakan jadi seperti berikut:

1. fun main() {
2.     val officeOpen = 7
3.     val officeClosed = 16
4.     val now = 20
5.  
6.     val isOpen = now >= officeOpen && now <= officeClosed
7.  
8.     print("Office is open : $isOpen")
9.     /*
10.         Output : Office is open : false
11.      */
12. }

Disjunction atau OR (||)


Berbeda dengan operator AND (&&), operator OR (||) akan mengembalikan nilai true jika
hasil evaluasi dari salah satu expressions yang diberikan bernilai true.

1. fun main() {
2.     val officeOpen = 7
3.     val officeClosed = 16
4.     val now = 20
5.  
6.     val isClose = now < officeOpen || now > officeClosed
7.  
8.     print("Office is closed : $isClose")
9.     /*
10.         Output : Office is closed : true
11.      */
12. }

Variabel isClose di atas bernilai true. Alasannya, hasil evaluasi salah satu expression yang


diberikan, bernilai true, yaitu expression disebelah kanan.

Negation atau NOT (!) 


Berbeda dengan operator AND (&&) dan operator OR(||), operator NOT(!) digunakan untuk
melakukan negasi pada hasil evaluasi expression yang diberikan. Contoh, Jika
hasil expressions setelah dievaluasi bernilai true, maka operator NOT akan mengembalikan
nilai false.

1. fun main() {
2.     val officeOpen = 7
3.     val now = 10
4.     val isOpen = now > officeOpen
5.  
6.     if (!isOpen) {
7.         print("Office is closed")
8.     } else {
9.         print("Office is open")
10.     }
11.  
12.     /*
13.         Output : Office is open
14.      */
15. }

Hasil evaluasi expression di atas adalah true. Tapi ketika menggunakan operator NOT


maka akan dinegasikan menjadi nilai false. Sehingga statement pada branch else-lah yang
akan dijalankan.
Numbers
Pada modul tipe data kita sudah mempelajari tentang beberapa tipe
seperti Character, String dan Array. Sekarang kita akan mempelajari beberapa tipe data
yang termasuk ke dalam tipe Number. Number adalah sebuah tipe data yang khusus
digunakan untuk menyimpan nilai dalam bentuk numerik.

Di Kotlin, tipe data Number disimpan dengan cara yang berbeda. Beberapa tipe bawaan
yang merepresentasikan Numbers adalah Double, Long, Int, Short dan Byte. Setiap tipe
data Number memiliki ukuran (satuan Bit) berbeda-beda, tergantung besaran nilai yang
dapat simpan.

 Int (32 Bit)

Int adalah tipe data yang umumnya digunakan untuk menyimpan nilai numerik. Int
dapat menyimpan data dari range -2^31 sampai +2^31-1. Dengan ukuran 32 Bit kita
bisa menggunakannya untuk menyimpan nilai yang besar. Catatannya, tetap lihatlah
batasan nilai maksimal yang dapat dimasukkan.

1. val intNumber = 100


 Long (64 Bit)

Long adalah tipe data yang digunakan untuk menyimpan nilai numerik yang lebih
besar yaitu dari range -2^63 sampai +2^63-1. Long bisa didefinisikan secara
eksplisit:

1. val longNumber: Long = 100

Atau dengan menambahkan suffix L seperti berikut:

2. val longNumber = 100L


 Short (16 Bit)

Short merupakan sebuah bilangan bulat yang hanya dapat menyimpan nilai yang
kecil karena hanya berukuran 16 Bit.

1. val shortNumber: Short = 10


 Byte (8 Bit)

Dengan ukuran yang kecil, Byte hanya mampu menyimpan nilai yang kecil sama
halnya seperti Short. Byte biasa digunakan untuk keperluan proses membaca dan
menulis data dari sebuah stream file atau jaringan.

1. val byteNumber = 0b11010010


 Double (64 Bit)

Sama halnya dengan Long yang memiliki ukuran yang besar, Double mampu
menyimpan nilai numerik yang besar pula. Pada umumnya Double digunakan untuk
menyimpan nilai numerik pecahan sampai dengan maksimal 15-16 angka di
belakang koma.

1. val doubleNumber: Double = 1.3


 Float (32 Bit)

Sama seperti Double, namun memiliki ukuran yang lebih kecil, yakni hanya sampai
6-7 angka di belakang koma.

1. val floatNumber: Float = 0.123456789f //yang terbaca hanya 0.1234567

Untuk mengetahui nilai maksimal yang dapat disimpan oleh suatu tipe Number, kita bisa
menggunakan properti MAX_VALUE.  Sementara untuk mengetahui nilai minimal yang
dapat disimpan, gunakan properti MIN_VALUE.   

1. fun main() {
2.     val maxInt = Int.MAX_VALUE
3.     val minInt = Int.MIN_VALUE
4.  
5.     println(maxInt)
6.     println(minInt)
7.  
8.     /*
9.      output :
10.             2147483647
11.             -2147483648
12.      */
13. }

Jika kita memasukan nilai melebihi nilai maksimal yang dapat disimpan, maka akan
terjadi overflow. Nilai yang akan dikembalikan adalah nilai minimal yang dapat disimpan.

1. fun main() {
2.     val maxInt = Int.MAX_VALUE
3.     val overRangeInt = Int.MAX_VALUE + 1 // This operation has led to an overflow
4.  
5.     println("Max Int: $maxInt")
6.     println("Over range Int: $overRangeInt")
7. }
8.  
9. /*
10. Output :
11.  
12. Max Int: 2147483647
13. Over range Int: -2147483648
14. */

Terdapat beberapa operator matematika pada tipe data Number seperti penjumlahan (+),
pengurangan (-), perkalian (*) , pembagian (/) dan modulus (%, atau sisa hasil bagi).

1. // main function
2. fun main() {
3.     val numberOne = 1
4.     val numberTwo = 2
5.  
6.     print(numberOne + numberTwo)
7.     /*
8.         output : 3
9.      */
10. }
Perlu diketahui, hasil operasi pembagian pada tipe data Int akan dibulatkan kebawah.
Contohnya seperti berikut:

1. // main function
2. fun main() {
3.     val numberOne: Int = 20
4.     val numberTwo: Int = 10
5.  
6.     print(numberOne / numberTwo)
7.     /*
8.         output : 2
9.      */
10. }

Sama seperti perhitungan matematika di mana operasi perkalian dan pembagian


didahulukan, demikian halnya perhitungan pada Kotlin.

1. fun main() {
2.     print(5 + 4 * 4)
3.     /*
4.      output: 21
5.      */
6. }

Operasi 4 * 4 akan dilakukan terlebih dahulu, kemudian diikuti 5 + 16. Jika ingin operasi 5 +
4 dilakukan terlebih dahulu, gunakan tanda kurung:

1. fun main() {
2.     print((5 + 4) * 4)
3.     /*
4.      output: 36
5.      */
6. }

Di Kotlin kita tidak bisa melakukan konversi secara langsung. Contoh, ketika ingin
melakukan konversi dari tipe data Byte ke tipe data Int.

1. fun main() {
2.    val byteNumber: Byte = 1
3.    val intNumber: Int = byteNumber // compile error
4. }

Kode akan gagal dikompilasi dengan log eror berikut:

Error:(4, 18) Kotlin: Type mismatch: inferred type is Byte but Int was expected

Untuk mengatasinya, lakukan konversi dengan bantuan beberapa fungsi


seperti toInt() berikut:

1. fun main() {
2.     val byteNumber: Byte = 10
3.     val intNumber: Int = byteNumber.toInt() // ready to go
4. }

Kode di atas menggunakan fungsi toInt() untuk melakukan konversi secara eksplisit dari tipe
data Byte ke tipe data Int. Adapun beberapa fungsi konversi yang dapat kita gunakan antara
lain:

 toByte(): Byte
 toShort(): Short
 toInt(): Int
 toLong(): Long
 toFloat(): Float
 toDouble(): Double
 toChar(): Char
Contoh lain penggunaan konversi adalah sebagai berikut:

1. fun main() {
2.     val stringNumber = "23"
3.     val intNumber = 3
4.  
5.     print(intNumber + stringNumber.toInt())
6.     /*
7.      output: 26
8.      */
9. }

Dengan fungsi konversi di atas, nilai 23 yang semula bertipe String di konversi ke tipe Int
yang kemudian dimasukan ke dalam operasi matematika.

Dengan Kotlin kita juga bisa menuliskan nilai numerik yang “readable” dengan
menggunakan tanda underscores seperti berikut:

1. fun main() {
2.     val readableNumber = 1_000_000
3.     print(readableNumber)
4.  
5.     /*
6.      output : 1000000
7.      */
8. }
Arrays
Selanjutnya adalah Array, yakni tipe data yang memungkinkan kita untuk menyimpan
beberapa objek di dalam sebuah variabel. Array di Kotlin direpresentasikan oleh
kelas Array yang memiliki fungsi get dan set serta properti size. Untuk membuat sebuah
Array kita bisa memanfaatkan sebuah library function arrayOf() seperti berikut:

1. val array = arrayOf(1, 3, 5, 7)

Kita juga dapat memasukkan nilai dengan berbagai jenis tipe data ke
dalam arrayOf() misalnya:

1. val mixArray = arrayOf(1, 3, 5, 7 , "Dicoding" , true)

Kotlin juga memungkinkan kita untuk membuat Array dengan tipe data primitif dengan
memanfaatkan beberapa fungsi spesifik berikut:

 intArrayOf() : IntArray
 booleanArrayOf() : BooleanArray
 charArrayOf() : CharArray
 longArrayOf() : LongArray
 shortArrayOf() : ShortArray
 byteArrayOf() : ByteArray
Jika kita ingin membuat Array yang hanya bisa dimasukkan nilai dengan tipe
data Int, gunakan intArrayOf(), misalnya: 

1. val intArray = intArrayOf(1, 3, 5, 7)

Kita juga bisa mendapatkan nilai tunggal dari sekumpulan nilai yang berada di dalam
sebuah Array dengan memanfaatkan indexing seperti berikut:

1. fun main() {
2.     val intArray = intArrayOf(1, 3, 5, 7)
3.     print(intArray[2])
4. }
5.  
6. /*
7.    Output: 5
8. */

Nilai 2 pada kode di atas merupakan indeks atau posisi dari nilai tunggal yang ingin kita
dapatkan. Perlu diketahui bahwa sebuah indeks selalu dimulai dari 0. Selain mendapatkan
nilai tunggal, dengan indexing kita juga bisa mengubah nilai tunggal tersebut. Sebagai
contoh:

1. fun main() {
2.     val intArray = intArrayOf(1, 3, 5, 7)  // [1, 3, 5, 7]
3.     intArray[2] = 11                       // [1, 3, 11, 7]
4.  
5.     print(intArray[2])
6. }
7.  
8. /*
9.    Output: 11
10. */

Selain menggunakan library function arrayOf() dalam pembuatan objek Array, kita juga bisa


menggunakan Array(). Constructor pada Array() membutuhkan 2 argumen yaitu size dan
fungsi lambda. Contoh untuk membuat Array menggunakan Array() adalah seperti berikut:
1. val intArray = Array(4, { i -> i * i }) // [0, 1, 4, 9]

Pada kode di atas kita menentukan angka 4 sebagai size Array. Fungsi lambda di atas ada
dua. Pertama, untuk mengambil indeks Array yang akan digunakan sebagai argumen.
Kedua, menentukan elemen Array yang akan dimasukkan ke dalam indeks tersebut.

Apa itu library Function?


Library Function merupakan sekumpulan fungsi bawaan Kotlin Standart Library yang dapat digunakan untuk
menerapkan suatu komponen dengan cara yang sederhana dengan memanfaatkan Lambda Expression.
Ketika mengembangkan sebuah program, ada satu hal yang tak boleh kita abaikan. Ia
adalah NullPointerException (NPE), sebuah kesalahan yang terjadi saat ingin mengakses
atau mengelola nilai dari sebuah variabel yang belum diinisialisasi atau variabel yang
bernilai null. Karena sangat umum terjadi dan bisa berakibat fatal, NPE terkenal dengan
istilah “The Billion Dollar Mistake”.

Dalam penanganannya, kita harus berhati-hati karena NPE menyebabkan aplikasi yang kita
kembangkan, rusak saat dijalankan.

Pada Kotlin kita dimudahkan untuk mengelola variabel nullable sehingga dapat


meminimalisir terjadinya NullPointerException. Kotlin hadir dengan
penanganan nullability yang mudah. Kotlin mampu membedakan objek yang boleh
bernilai null dan objek yang tidak boleh bernilai null pada saat objek tersebut dibuat. 

1. val text: String = null // compile time error

Kotlin akan memaksa kita untuk menentukan nilai awal dari sebuah objek ketika dibuat dan
tidak boleh bernilai null. Jika  ingin sebuah objek bisa bernilai null, kita bisa menambahkan
tanda ? setelah menentukan tipe dari objek tersebut:

1. val text: String? = null // ready to go

Namun kita tidak bisa langsung mengakses atau mengelola nilai dari objek yang sudah kita
tandai sebagai nullable. Sebagai contoh:

1. val text: String? = null


2. val textLength = text.length // compile time error

Ketika kita menuliskan kode di atas, maka akan gagal dikompilasi dengan log eror berikut:

Error:(4, 26) Kotlin: Only safe (?.) or non-null asserted (!!.) calls are allowed on a
nullable receiver of type String?

Lalu bagaimana cara kita mengakses atau mengelola nilai dari objek yang ditandai
sebagai nullable? Cara mudahnya, periksa objek tersebut apakah bernilai null atau tidak: 

1. val text: String? = null


2.  
3. //val textLength = text.length // compile time error
4.  
5. if (text != null){
6.     val textLength = text.length // ready to go
7. }

Dengan cara tradisional menggunakan if/else seperti di atas, kompiler akan mengizinkan


kita untuk mengelola nilai dari sebuah variabel yang kita tandai sebagai nullable.

Pada modul selanjutnya kita akan mempelajari penanganan objek yang ditandai
sebagai nullable dengan cara yang lebih mudah. Kita akan menggunakan Safe
Calls dan Elvis Operator di Kotlin. 
Di modul sebelumnya kita sudah mengenal tentang NullPointerException dan cara
penanganannya. Kini kita akan belajar bagaimana menangani objek nullable dengan cara
yang lebih mudah, yaitu menggunakan Safe Calls dan Elvis Operator.

Safe calls operator (?.)


Yang pertama akan kita pelajari adalah Safe Calls. Seperti namanya, safe call akan
menjamin kode yang kita tulis aman dari NullPointerException. Dalam menggunakan safe
call, kita akan mengganti tanda titik (.) dengan tanda (?.) saat mengakses atau mengelola
nilai dari objek nullable. Seperti ini: 

1. val text: String? = null


2. text?.length

Dengan safe call, kompiler akan melewatkan proses jika objek tersebut bernilai null.

Elvis Operator (?:)


Elvis operator memungkinkan kita untuk menetapkan default value atau nilai dasar jika
objek bernilai null.

1. val text: String? = null


2. val textLength = text?.length ?: 7

Kode di atas sebenarnya sama seperti ketika kita menggunakan if/else berikut:

1. val textLength = if (text != null) text.length else 7

Elvis akan mengembalikan nilai text.length jika text tidak bernilai null. Sebaliknya,


jika text bernilai null maka default value yang akan dikembalikan.

Sebelum lanjut ke modul selanjutnya terdapat satu hal yang perlu diperhatikan dalam
penanganan objek nullable. Perhatikan penggunaan operator non-null assertion (!!),
misalnya seperti berikut:

1. val text: String? = null


2. val textLength = text!!.length // ready to go ???

Dengan menggunakan non-null assertion kompiler akan mengizinkan kita untuk


mengakses atau mengelola nilai dari sebuah objek nullable. Namun penggunaan operator
tersebut sangat tidak disarankan karena akan memaksa sebuah objek menjadi non-
null. Sehingga ketika objek tersebut bernilai null, Anda tetap akan berjumpa
dengan NullPointerException.
String Template
Di beberapa modul sebelumnya Anda sudah melihat bagaimana sebuah String ditulis seperti
berikut:

1. "First character of $text is $firstChar"

Lantas dinamakan apakah mekanisme penulisan String seperti di atas? Kotlin mendukung
sebuah fitur bernama String Template. Sebuah fitur yang memungkinkan kita untuk
menyisipkan sebuah variabel ke dalam sebuah String tanpa concatenation (penggabungan
objek String menggunakan +) seperti berikut:

1. fun main() {
2.     val name = "Kotlin"
3.     print("My name is " + name)
4. }
5. /*
6.    output : My name is Kotlin
7. */

Untuk menggunakan string template, kita hanya perlu menambahkan karakter $ sebelum


nama variabel yang akan disisipkan seperti berikut:

1. fun main() {
2.     val name = "Kotlin"
3.     print("My name is $name")
4. }
5. /*
6.    output : My name is Kotlin
7. */

Variabel yang dapat disisipkan tidak sebatas String. Kita juga bisa menyisipkan objek lain
misal Int atau Double seperti berikut:

1. fun main() {
2.     val name = "Kotlin"
3.     val old = 3
4.     print("My name is $name, im $old years old")
5. }
6. /*
7.    output : My name is Kotlin, im 3 years old
8. */

Tidak hanya sampai disitu, Anda juga bisa menyisipkan sebuah expression ke dalam
sebuah string template. Caranya, sisipkan expression ke dalam curly braces yang diikuti
karakter $.

1. fun main() {
2.     val hour = 7
3.     print("Office ${if (hour > 7) "already close" else "is open"}")
4. }
5. /*
6.    output : Office is open
7. */

Dengan string template, kita lebih mudah membuat objek String yang dinamis.
Sebelum Anda lanjut ke modul berikutnya, berikut adalah beberapa rangkuman dari sub-
modul yang sudah dipelajari:

 Sama seperti bahasa pemrograman lain, Kotlin memiliki fungsi untuk mencetak nilai
pada console yaitu fungsi print() dan println().
 Untuk mendeklarasi variable, Anda akan menggunakan kata kunci var ata val. var
atau val digunakan untuk mengontrol nilai dari sebuah variabel. Dengan kata kunci
var kita bisa mengubah nilai yang sudah kita inisialisasikan.
 Untuk membuat variable yang menampung data berupa text, Anda dapat
menggunakan tipe data Char untuk menyimpan satu karakter dan tipe
data String untuk menyimpan beberapa karakter.
 Sedangkan untuk menampung data berupa number, Anda akan menggunakan
beberapa tipe data di bawah ini:
o Int (32 Bit)
Int adalah tipe data yang umumnya digunakan untuk menyimpan nilai
numerik. Int dapat menyimpan data dari range -2^31 sampai +2^31-1.
Dengan ukuran 32 Bit kita bisa menggunakannya untuk menyimpan nilai
yang besar. Catatannya, tetap lihatlah batasan nilai maksimal yang dapat
dimasukkan.
o Long (64 Bit)
Long adalah tipe data yang digunakan untuk menyimpan nilai numerik yang
lebih besar yaitu dari range -2^63 sampai +2^63-1.
o Short (16 Bit)
Short merupakan sebuah bilangan bulat yang hanya dapat menyimpan nilai
yang kecil karena hanya berukuran 16 Bit.
o Byte (8 Bit)
Dengan ukuran yang kecil, Byte hanya mampu menyimpan nilai yang kecil
sama halnya seperti Short. Byte biasa digunakan untuk keperluan proses
membaca dan menulis data dari sebuah stream file atau jaringan.
o Double (64 Bit)
Sama halnya dengan Long yang memiliki ukuran yang besar, Double mampu
menyimpan nilai numerik yang besar pula. Pada umumnya Double digunakan
untuk menyimpan nilai numerik pecahan.
 Selanjutnya adalah Array, yakni tipe data yang memungkinkan Anda untuk
menyimpan beberapa objek di dalam satu variabel.
 Kotlin juga memungkinkan Anda untuk membuat Array dengan tipe data primitif
dengan memanfaatkan beberapa fungsi spesifik seperti berikut:
o intArrayOf() : IntArray
o booleanArrayOf() : BooleanArray
o charArrayOf() : CharArray
o longArrayOf() : LongArray
o shortArrayOf() : ShortArray
o byteArrayOf() : ByteArray
 Kotlin mendukung juga tipe data Boolean di mana tipe data yang hanya memiliki dua
nilai, yaitu true dan false. Selain itu, Terdapat 3 (tiga) operator yang dapat digunakan
pada Boolean:
o Conjunction atau AND (&&)
Operator AND (&&) akan mengembalikan nilai true jika semua hasil evaluasi
expression yang diberikan bernilai true.
o Disjunction atau OR (||)
Berbeda dengan operator AND (&&), operator OR (||) akan mengembalikan
nilai true jika hasil evaluasi dari salah satu expressions yang diberikan
bernilai true.
o Negation atau NOT (!)
Berbeda dengan operator AND (&&) dan operator OR(||), operator NOT(!)
digunakan untuk melakukan negasi pada hasil evaluasi expression yang
diberikan.
 Jika ingin menginisialisasi nilai dari sebuah variabel berdasarkan suatu kondisi.
Untuk menyelesaikannya, gunakan If Expression.
 Dengan Kotlin kita mudah dalam mengelola variable nullable sehingga dapat
meminimalisir terjadinya NullPointerException dengan menggunakan Safe
Call dan Elvis Operator.
 Function atau fungsi merupakan sebuah prosedur yang memiliki keterkaitan dengan
pesan dan objek. Ketika kita memanggil sebuah fungsi maka sebuah mini-
program akan dijalankan. Fungsi sendiri bisa diartikan sebagai cara sederhana untuk
mengatur program buatan kita.

Kita sudah selesai dengan Kotlin Fundamental di mana Anda sudah mempelajari tentang
bagaimana menampilkan pesan pada konsol, berbagai macam tipe data, dan bagaimana
cara meminimalisir terjadinya NullPointerException. Selanjutnya kita mempelajari modul
Control Flow. Langsung saja ke modul berikutnya.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari konsep - konsep fundamental pada Kotlin seperti tipe data, fungsi,
If expression, dan juga fitur Null Safety.

Untuk menguji pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu
mengerjakan beberapa latihan sederhana. Buka kembali proyek latihan dan kerjakan semua
latihan yang ada pada modul Kotlin Fundamental.
Kita telah belajar mengenai If Expression pada modul sebelumnya. If Expression
merupakan salah satu bagian dari Control Flow. Pada modul ini kita akan mempelajari
tentang apa itu control flow dan juga beberapa bagian lain yang ada di dalamnya.

Apa itu Control Flow?


Ketika kita mengembangkan sebuah program, tentu kita harus tahu seperti apa alurnya.
Control flow adalah cara kita mengontrol alur dari sebuah program berdasarkan kondisi saat
program tersebut berjalan.

Terdapat beberapa bagian dari control flow yang akan kita pelajari, antara lain:

 Enumeration
 When Expression
 Expression & Statement
 While and Do While
 Range and For Loop
 Break and Continue Labels

Kita akan mempelajari seperti apa dan bagaimana cara menerapkan beberapa control flow
di atas.
Enumeration
Enumeration merupakan salah satu fitur yang bisa kita gunakan untuk menyimpan kumpulan
objek yang telah didefinisikan menjadi tipe data konstanta. Enumeration dapat ditetapkan
sebagai nilai ke dalam sebuah variabel dengan cara yang lebih efisien. Selain itu,
Enumeration juga dapat digunakan untuk meminimalisir kesalahan dalam pengetikan nilai
sebuah variabel, misalnya:

1. val colorRed = 0xFF0000


2. val colorGreen = 0x00FF00
3. val colorBlue = 0x0000FF

Nilai dari beberapa variabel di atas berpotensi salah atau tertukar dengan nilai variabel lain.
Untuk meminimalisir kesalahan, kita memerlukan Enumeration. Anda bisa melakukannya
seperti ini: 

1. fun main() {
2.     val colorRed = Color.RED
3.     val colorGreen = Color.GREEN
4.     val colorBlue = Color.BLUE
5. }
6.  
7. enum class Color(val value: Int) {
8.     RED(0xFF0000),
9.     GREEN(0x00FF00),
10.     BLUE(0x0000FF)
11. }

Selain meminimalisir terjadinya kesalahan pengetikan, Enumeration juga membuat kode


yang kita tulis jadi lebih bersih dan mudah dibaca.

Untuk mendefinisikan sebuah kelas Enum, kita bisa menggunakan kata kunci enum dan


setiap objek yang berada di dalamnya dipisahkan oleh karakter koma (,). Selain itu, objek
yang berada di dalam Enum secara implisit bersifat static dan final sehingga kita tidak dapat
mengubahnya setelah dideklarasikan. Berikut contoh implementasi yang paling mendasar
dari sebuah Enum:

1. enum class Color{


2.     RED, GREEN, BLUE
3. }

Objek yang telah didefinisikan menjadi tipe data Enum dapat


mengakses attribute atau method di dalam kelas Enum itu sendiri. Konsep ini sama halnya
seperti Array. Bedanya, Enum berbentuk constant. Berikut contoh ketika kita ingin
mengakses objek yang berada di dalam kelas Enum:

1. fun main() {
2.     val color : Color = Color.RED
3.     print(color)
4. }
5.  
6. enum class Color{
7.     RED, GREEN, BLUE
8. }
9.  
10. /*
11.    output: RED
12. */
Seperti yang dicontohkan di awal, setiap objek yang dideklarasikan merupakan instance dari
kelas Enum tersebut. Kita bisa menginisialisasinya seperti berikut:

1. enum class Color(val value: Int) {


2.     RED(0xFF0000),
3.     GREEN(0x00FF00),
4.     BLUE(0x0000FF)
5. }

Selain itu, kita juga dapat mendeklarasikan anonymous class untuk setiap objek Enum,
misalnya: 

1. enum class Color(val value: Int) {


2.     RED(0xFF0000){
3.         override fun printValue() {
4.             println("value of RED is $value")
5.         }
6.     },
7.     GREEN(0x00FF00){
8.         override fun printValue() {
9.             println("value of GREEN is $value")
10.         }
11.     },
12.     BLUE(0x0000FF){
13.         override fun printValue() {
14.             println("value of BLUE is $value")
15.         }
16.     };
17.  
18.     abstract fun printValue()
19. }

Setiap enum class memiliki sebuah synthetic method yang memungkinkan kita


mendapatkan daftar objek Enum dan nama dari tiap Enum itu sendiri.

1. fun main() {
2.     val colors: Array<Color> = Color.values()
3.     colors.forEach { color ->
4.         print(color)
5.     }
6. }
7.  
8. enum class Color(val value: Int) {
9.     RED(0xFF0000),
10.     GREEN(0x00FF00),
11.     BLUE(0x0000FF)
12. }
13.  
14. /*
15.    output : RED, GREEN, BLUE
16. */
17.  
18.  

Perhatikan. Untuk mendapatkan daftar objek Enum kita bisa menggunakan fungsi values().
Sedangkan untuk mendapatkan nama dari objek Enum kita bisa menggunakan
fungsi valueOf() seperti berikut:

1. fun main() {
2.     val color: Color = Color.valueOf("RED")
3.     print("Color is $color")
4. }
5.  
6. enum class Color(val value: Int) {
7.     RED(0xFF0000),
8.     GREEN(0x00FF00),
9.     BLUE(0x0000FF)
10. }
11.  
12. /*
13.    output : Color is RED
14. */

Saat menggunakan fungsi valueOf() , perhatikan argumen yang kita masukkan ke dalam


fungsi tersebut. Jika argumen yang kita masukan tidak sama dengan salah satu objek enum
maka akan terjadi kesalahan IllegalArgumentException.

Apa itu IllegalArgumentException?


IllegalArgumentException adalah sebuah kondisi di mana saat ingin menggunakan sebuah fungsi, kita
menyematkan argumen yang tidak sesuai ke dalam fungsi tersebut [4].

Selain menggunakan fungsi values() dan fungsi valueOf(), kita bisa mendapatkan daftar


objek Enum dan nama dari objek Enum dengan cara yang lebih umum. Caranya, gunakan
fungsi enumValues() dan fungsi enumValueOf(). Contoh penggunaan kedua fungsi tersebut
adalah sebagai berikut:

1. fun main() {
2.     val colors: Array<Color> = enumValues()
3.     colors.forEach {color ->
4.         println(color)
5.     }
6.  
7.     val color: Color = enumValueOf("RED")
8.     println("Color is $color")
9. }
10.  
11. enum class Color(val value: Int) {
12.     RED(0xFF0000),
13.     GREEN(0x00FF00),
14.     BLUE(0x0000FF)
15. }
16.  
17. /*
18.    output :
19.        RED
20.        GREEN
21.        BLUE
22.        Color is RED
23.  
24. */

Konsep dari Enumeration sendiri sama seperti Array. Oleh karena itu, selain mendapatkan
daftar dan nama dari tiap objek Enum, kita juga bisa mendapatkan posisi tiap objek
menggunakan properti ordinal seperti berikut:

1. fun main() {
2.     val color: Color = Color.GREEN
3.  
4.     print("Position GREEN is ${color.ordinal}")
5. }
6.  
7. enum class Color(val value: Int) {
8.     RED(0xFF0000),
9.     GREEN(0x00FF00),
10.     BLUE(0x0000FF)
11. }
12.  
13. /*
14.    output : Position GREEN is 1
15.  
16. */

Di atas telah disebutkan bahwa setiap objek merupakan instance dari enum class yang kita
definisikan. Lantas bagaimana cara kita mengecek instance dari Enum itu sendiri? Nah,
untuk mengeceknya, gunakan When Expression seperti berikut:

1. fun main() {
2.     val color: Color = Color.GREEN
3.  
4.     when(color){
5.         Color.RED -> print("Color is Red")
6.         Color.BLUE -> print("Color is Blue")
7.         Color.GREEN -> print("Color is Green")
8.     }
9. }
10.  
11. enum class Color(val value: Int) {
12.     RED(0xFF0000),
13.     GREEN(0x00FF00),
14.     BLUE(0x0000FF)
15. }
16.  
17. /*
18.    output : Color is Green
19.  
20. */

Ketika menggunakan when untuk mengecek instance dari Enum, lebih baik masukkan


setiap objek Enum yang kita definisikan. Jika kita melewatkan salah satu objek,  peringatan
berikut akan muncul: ‘when' expression on enum is recommended to be exhaustive
Ketika mempelajari sebuah bahasa pemrograman, kita selalu dihadapkan dengan
istilah expressions dan statement. Ini adalah 2 (dua) istilah yang kadang salah dipahami. 

Untuk lebih mudah memahaminya, marilah kita lihat contoh


kode If sebagai statement berikut:

1. val openOffice = 7
2. val now = 8
3. if (now > openOffice)
4.     print("Office already open")
5. else
6. print("Office close")

Pada contoh kode di atas, if dikatakan sebagai statement karena ia tidak mengembalikan
nilai apapun, hanya sebagai percabangan sebagai pada bahasa Java.

Namun yang menarik If pada bahasa Kotlin juga bisa digunakan sebagai expression. Yang
dimaksud dengan expression adalah statement yang dapat mengembalikan nilai dan bisa
kita simpan ke dalam sebuah variabel seperti contoh berikut:

1. val openOffice = 7
2. val now = 8
3. val office = if (now > openOffice) "Office already open" else "Office close"
4. print(office)

Pada kode di atas, If akan mengembalikan nilai kedalam variabel office. Apabila kondisi
pada if terpenuhi maka variabel office akan berisi "Office already open" dan jika tidak maka
akan berisi "Office close".

Jadi, sudah paham kan perbedaan antara statement dan expression?

Nah, perlu Anda ketahui bahwa di dalam sebuah expression juga bisa terdapat


sebuah expression lagi. Contohnya seperti berikut:

1. fun main() {
2.     sum(1 , 1 * 4)
3. }
4.  
5. fun sum(value1: Int, value2: Int) = value1 + value2

Pada kode diatas 1 * 4 merupakan sebuah expression yang ada pada pemanggilan


fungsi sum() alias fungsi yang mengembalikan nilai. Setiap fungsi selalu mengembalikan
nilai. Alhasil, pemanggilan sebuah fungsi merupakan sebuah expression.

Contoh lain dari statement adalah inisialisasi sebuah variabel seperti berikut:

1. fun main() {
2.     val value1 = 10
3.     val value2 = 10
4.  
5.     sum(value1, value2)
6. }
7.  
8. fun sum(value1: Int, value2: Int) = value1 + value2
Pada kode di atas deklarasi variabel value1 dan value2 merupakan sebuah statement.
Sedangkan pemanggilan fungsi sum seperti yang dijelaskan di atas, merupakan
sebuah expression.
When Expressions
Untuk menentukan statement atau expression kita menggunakan If Expression.  Selain itu
kita juga bisa gunakan  When Expression,  yakni mekanisme yang memungkinkan nilai dari
sebuah variabel/expression, mampu mengubah alur program.

Contoh sederhana dalam penggunaan when expression adalah seperti berikut:

1. fun main() {
2.     val value = 7
3.  
4.     when(value){
5.         6 -> println("value is 6")
6.         7 -> println("value is 7")
7.         8 -> println("value is 8")
8.     }
9. }
10.  
11. /*
12.    output: value is 7
13. */

when akan mencocokan semua argumen yang berada di setiap branch secara berurutan


sampai salah satu kondisi terpenuhi. Di dalam when kita juga bisa
menambahkan branch else seperti berikut:

1. fun main() {
2.     val value = 20
3.  
4.     when(value){
5.         6 -> println("value is 6")
6.         7 -> println("value is 7")
7.         8 -> println("value is 8")
8.         else -> println("value cannot be reached")
9.     }
10. }
11.  
12. /*
13.    output: value cannot be reached
14. */

else akan dievaluasi jika tiada satupun kondisi yang terpenuhi pada branch sebelumnya.


Sama halnya seperti if expression, when expression dapat mengembalikan nilai dan dapat
disimpan di dalam sebuah variabel seperti berikut:

1. fun main() {
2.     val value = 7
3.     val stringOfValue = when (value) {
4.         6 -> "value is 6"
5.         7 -> "value is 7"
6.         8 -> "value is 8"
7.         else -> "value cannot be reached"
8.     }
9.  
10.     println(stringOfValue)
11. }
12.  
13. /*
14.    output : value is 7
15. */
else adalah hal wajib jika kita menggunakan when expression untuk mengembalikan nilai.
Bagaimana jika kita melewatkannya? Akan tampil eror berikut:
'when' expression must be exhaustive, add necessary 'else' branch

Jika kita memiliki dua atau lebih baris kode yang akan kita jalankan di setiap branch, kita
bisa memindahkannya ke dalam curly braces seperti berikut:

1. fun main() {
2.     val value = 7
3.     val stringOfValue = when (value) {
4.         6 -> {
5.             println("Six")
6.             "value is 6"
7.         }
8.         7 -> {
9.             println("Seven")
10.             "value is 7"
11.         }
12.         8 -> {
13.             println("Eight")
14.             "value is 8"
15.         }
16.         else -> {
17.             println("undefined")
18.             "value cannot be reached"
19.         }
20.     }
21.  
22.     println(stringOfValue)
23. }
24.  
25. /*
26.    output :
27.             Seven
28.             value is 7
29.  
30. */

when juga memungkinkan kita untuk memeriksa instance dengan tipe tertentu dari sebuah


objek menggunakan is atau !is. Contohnya seperti berikut:

1. fun main() {
2.     val anyType : Any = 100L
3.     when(anyType){
4.         is Long -> println("the value has a Long type")
5.         is String -> println("the value has a String type")
6.         else -> println("undefined")
7.     }
8. }
9.  
10. /*
11.    output : the value has a Long type
12. */

Selain itu, when expression juga bisa kita gunakan untuk memeriksa nilai yang terdapat
pada sebuah Range atau Collection. Range sendiri merupakan salah satu tipe data yang
unik di mana kita dapat menentukan nilai awal dan nilai akhir. Range dan Collection akan
dibahas terpisah pada modul berikutnya. 

Berikut adalah contoh saat kita hendak mengecek apakah sebuah nilai ada di dalam sebuah
Range atau tidak.
1. fun main() {
2.     val value =  27
3.     val ranges = 10..50
4.  
5.     when(value){
6.         in ranges -> println("value is in the range")
7.         !in ranges -> println("value is outside the range")
8.         else -> println("value undefined")
9.     }
10. }
11.  
12. /*
13.    output : value is in the range
14. */

Branch pertama pada contoh kode di atas akan memeriksa apakah nilai dari value terdapat
di cakupan nilai ranges. Kemudian untuk branch kedua akan memeriksa apakah nilai
dari value tidak terdapat pada nilai yang dicakup oleh ranges. Sedangkan branch else akan
mengevaluasi jika dua kondisi sebelumnya tidak terpenuhi.

Sejak Kotlin 1.3, kita dapat menangkap subjek dari when expression di dalam sebuah
variabel. Contohnya seperti berikut:

1. fun main() {
2.     val registerNumber = when(val regis = getRegisterNumber()){
3.         in 1..50 -> 50 * regis
4.         in 51..100 -> 100 * regis
5.         else -> regis
6.     }
7. }
8.  
9. fun getRegisterNumber() = Random.nextInt(100)

Perhatikan. Cakupan variabel yang dapat ditangkap, terbatas di dalam body when


expression.

Jika kita melihat penjelasan dan contoh penggunaan dari when expression di atas, ia
memiliki kesamaan dengan if expression. Lantas disituasi seperti apa kita
menggunakannya? if expression sebaiknya digunakan ketika kondisi yang diberikan tidak
lebih dari 2 (dua) dan kondisi yang diberikan tidak terlalu rumit.

1. val anyType : Any = 100L


2. if (anyType is Long){
3.     println("the value has a Long type")
4. } else {
5.     println("the value is not Long type")
6. }

Berbeda dengan when expression, ia bisa digunakan ketika kondisi yang diberikan lebih
dari 2 (dua). 

1. val anyType: Any = 100L


2. when (anyType) {
3.     is Long -> println("the value has a Long type")
4.     is Int -> println("the value has a Int type")
5.     is Double -> println("the value has a Double type")
6.     else -> println("undefined")
7. }
Bayangkan ketika kita ditugaskan untuk mencetak beberapa baris teks yang sama ke dalam
konsol seperti berikut:

1. Hello World
2. Hello World
3. Hello World
4. Hello World
5. Hello World

Kita pasti langsung terpikir akan menulis programnya seperti berikut:

1. fun main() {
2.     println("Hello World")
3.     println("Hello World")
4.     println("Hello World")
5.     println("Hello World")
6.     println("Hello World")
7. }

Think! Bagaimana jika teks yang harus ditampilkan berjumlah banyak? Tentu kita tidak
mungkin menuliskan fungsi println() sesuai dengan jumlah yang kita ingin tampilkan. 

Nah, untuk mengatasinya kita bisa menggunakan perulangan. Perulangan adalah proses
perulangan blok yang sama tanpa henti sampai kondisi yang diberikan tidak terpenuhi atau
bernilai false. 

Perulangan terdiri dari While, Do While dan For Loop. Modul ini akan membahas While
dan Do While. Sementara For Loop akan dibahas terpisah pada modul berikutnya.

While
Untuk menggunakan While, kita membutuhkan kata kunci while, lanjut ke kondisi di dalam
tanda kurung, dan diakhiri oleh blok body dari while itu sendiri. Berikut adalah contoh dari
penggunaan While:

1. fun main() {
2.     var counter = 1
3.     while (counter <= 7){
4.         println("Hello, World!")
5.         counter++
6.     }
7. }
8. /*
9.    output :
10.        Hello, World!
11.        Hello, World!
12.        Hello, World!
13.        Hello, World!
14.        Hello, World!
15.        Hello, World!
16.        Hello, World!
17. */

Perhatikan kondisi dari While di atas, selama nilai dari variabel counter kurang dari sama
dengan 7 maka kode yang di dalamnya akan terus dilakukan. Lalu ketika kondisi tersebut
sudah tak terpenuhi maka proses perulangan akan dihentikan.

While bersifat Entry Controlled Loop. Artinya, kondisi yang diberikan akan dievaluasi


terlebih dahulu. Jika kondisi tersebut terpenuhi maka proses perulangan akan dijalankan.
Jika kondisi yang diberikan tidak terpenuhi sejak awal maka proses perulangan tidak akan
dijalankan. Untuk mengujinya Anda bisa menulis dan menjalankan kode berikut:

1. fun main() {
2.     var counter = 8
3.     while (counter <= 7){
4.         println("Hello, World!")
5.         counter++
6.     }
7. }

Dengan While kita tidak perlu menuliskan fungsi println() secara berulang untuk mencetak
teks ke dalam konsol seperti contoh kasus di awal.

Do While
Selain menggunakan While, kita juga bisa menggunakan Do While untuk melakukan
perulangan seperti berikut:

1. fun main() {
2.     var counter = 1
3.     do {
4.         println("Hello, World!")
5.         counter++
6.     } while (counter <= 7)
7. }
8.  
9. /*
10.    output:
11.        Hello, World!
12.        Hello, World!
13.        Hello, World!
14.        Hello, World!
15.        Hello, World!
16.        Hello, World!
17.        Hello, World!
18. */

Berbeda dengan While, Do While bersifat Exit Controlled Loop di mana proses perulangan
akan langsung dijalankan di awal. Jika telah selesai, barulah kondisi yang diberikan akan
dievaluasi.

Saat menggunakan While dan Do While perhatikan infinite loop, yaitu kondisi di mana
proses perulangan berlangsung terus menerus sampai aplikasi menjadi crash. Contoh dari
infinite loop adalah seperti berikut:

1. fun main() {
2.     var value = 'A'
3.     do {
4.         print(value)
5.     } while (value <= 'Z')
6. }

Infinite loop terjadi jika kondisi yang diberikan selamanya terpenuhi atau bernilai true.

While dan Do While sendiri tidak dapat digunakan untuk melakukan perulangan pada rentan
angka. Untuk melakukannya kita bisa menggunakan For Loop yang akan kita pelajari pada
modul selanjutnya.
Range
Seperti yang disampaikan sebelumnya, Range merupakan salah satu tipe yang unik pada
kotlin. Kita dapat menentukan nilai awal dan nilai akhir pada Range. Range
direpresentasikan dengan operator .. atau dengan fungsi rangeTo() dan downTo().

Terdapat beberapa cara untuk membuat Range di Kotlin. Pertama, seperti berikut:

1. val rangeInt = 1..10

Kode diatas menggunakan operator .. untuk membuat Range. Variabel rangeInt di atas


mencakup nilai 1, 2, 3, 4, 5, 6, 7, 8, 9, 10. Jarak antara dua nilai yang dicakup, ditentukan
oleh step. Secara default, step bernilai 1. Untuk mendapatkan step kita bisa menggunakan
properti step seperti contoh berikut:

1. fun main() {
2.     val rangeInt = 1..10
3.     print(rangeInt.step)
4. }
5.  
6. /*
7.    output: 1
8. */

Dan untuk mengubah nilai dari step bisa dilakukan ketika kita menginisialisasi nilai yang
dicakup Range itu sendiri:

1. fun main() {
2.     val rangeInt = 1..10 step 2
3.     rangeInt.forEach {
4.         print("$it ")
5.     }
6.     println(rangeInt.step)
7. }
8.  
9. /*
10.    output: 1 3 5 7 9 2
11. */

Pada kode di atas kita menentukan nilai step adalah 2, maka nilai yang dicakup variabel
rangeInt adalah 1, 3, 5, 7, 9.

Selanjutnya adalah menggunakan fungsi rangeTo():

1. val rangeInt = 1.rangeTo(10)

Kode di atas, operator .. digantikan dengan fungsi rangeTo() untuk membuat Range . Nilai


yang dicakup pada kode di atas sama seperti kode sebelumnya ketika menggunakan
operator ...

Kita juga bisa menentukan nilai yang dicakup pada Range dengan urutan terbalik seperti
berikut:

1. val downInt = 10.downTo(1)

Kode di atas menggunakan fungsi downTo() untuk menentukan nilai dengan urutan terbalik.


Variabel downInt di atas mencakup nilai 10, 9, 8, 7, 6, 5, 4, 3, 2, 1.

Kita juga bisa memeriksa apakah suatu nilai ada pada cakupan nilai Range.
1. fun main() {
2.     val tenToOne = 10.downTo(1)
3.     if (7 in tenToOne) {
4.         println("Value 7 available")
5.     }
6. }
7. /*
8.    output: Value 7 available
9. */

Pada kode di atas kita menggunakan kata kunci in untuk memeriksa apakah 7 berada
diantara kisaran 1 sampai 10. Expression yang dievaluasi pada if di atas sama seperti ketika
menggunakan expression berikut:

1. fun main() {
2.     if (1 <= 7 && 7 <= 10){
3.         println("Value 7 available")
4.     }
5. }
6. /*
7.    output: Value 7 available
8. */

Nah,  di atas kita telah memeriksa apakah suatu nilai ada pada nilai cakupan Range.
Sebaliknya, kita juga bisa memeriksa apakah suatu nilai tidak ada pada nilai cakupan
Range tersebut. Kita bisa menggunakan kata kunci !in seperti ini:

1. fun main() {
2.     val tenToOne = 10.downTo(1)
3.     if (11 !in tenToOne) {
4.         println("No value 11 in Range ")
5.     }
6. }
7. /*
8.    output: No value 11 in Range
9. */

Range pada Kotlin mendukung beberapa tipe integral


seperti IntRange, LongRange dan CharRange. Sehingga selain nilai numerik, kita juga
bisa menentukan tipe Character sebagai nilai yang dicakup oleh Range:

1. val rangeChar = 'A'.rangeTo('F')

Nilai pada variable rangeChar di atas mencakup A, B, C, D, E, F.


For Loop
Sama seperti While dan Do While, For merupakan konsep perulangan pada blok yang
sama selama hasil evaluasi kondisi yang diberikan terpenuhi atau bernilai true. For dapat
digunakan pada Ranges, Collections, Arrays dan apapun yang menyediakan iterator.
Contoh dari For loop sendiri adalah sebagai berikut:

1. fun main() {
2.     val ranges = 1..5
3.     for (i in ranges){
4.         println("value is $i!")
5.     }
6. }
7.  
8. /*
9.    output :
10.        value is 1!
11.        value is 2!
12.        value is 3!
13.        value is 4!
14.        value is 5!
15.  
16. */

Kode di atas merupakan contoh ketika melakukan perulangan pada Ranges dengan
menggunakan range expression yang sudah kita pelajari sebelumnya. Karena
menggunakan range expression, kita juga dapat menuliskannya seperti berikut:

1. fun main() {
2.     val ranges = 1.rangeTo(5)
3.     for (i in ranges){
4.         println("value is $i!")
5.     }
6. }
7.  
8. /*
9.    output :
10.        value is 1!
11.        value is 2!
12.        value is 3!
13.        value is 4!
14.        value is 5!
15.  
16. */

Selain itu, kita juga dapat menuliskan For loop menggunakan range expression seperti
berikut:

1. fun main() {
2.     val ranges = 1.rangeTo(10) step 3
3.     for (i in ranges ){
4.         println("value is $i!")
5.     }
6. }
7.  
8. /*
9.    output :
10.        value is 1!
11.        value is 4!
12.        value is 7!
13.        value is 10!
14. */
Pada kode di atas, kita menambahkan ekstensi step yang akan mengembalikan nilai baru
dengan tipe IntProgression dengan jarak nilai sebelumnya adalah 3.

Kita juga dapat mengakses indeks untuk setiap elemen yang ada pada Ranges dengan
memanfaatkan fungsi withIndex() seperti berikut:

1. fun main() {
2.     val ranges = 1.rangeTo(10) step 3
3.     for ((index, value) in ranges.withIndex()) {
4.         println("value $value with index $index")
5.     }
6. }
7. /*
8.    output :
9.        value 1 with index 0
10.        value 4 with index 1
11.        value 7 with index 2
12.        value 10 with index 3
13. */

Kita menggunakan kata kunci for untuk memulai proses perulangan. Untuk tujuan yang
sama, kita juga bisa loh, memanfaatkan salah satu ekstensi pada Kotlin yaitu forEach.
Contohnya seperti berikut:

1. fun main() {
2.     val ranges = 1.rangeTo(10) step 3
3.     ranges.forEach { value ->
4.         println("value is $value!")
5.     }
6. }
7.  
8. /*
9.    output :
10.        value is 1!
11.        value is 4!
12.        value is 7!
13.        value is 10!
14. */

forEach pada kode di atas merupakan sebuah lambda expression yang hanya memiliki satu
argumen yaitu nilai tunggal yang dicakup pada ranges. Jika kita mendapatkan indeks dari
tiap nilai yang dicakup kita bisa menggunakan ekstensi forEachIndexed seperti berikut:

1. fun main() {
2.  
3.     val ranges = 1.rangeTo(10) step 3
4.     ranges.forEachIndexed { index, value ->
5.         println("value $value with index $index")
6.     }
7. }
8. /*
9.    output :
10.        value 1 with index 0
11.        value 4 with index 1
12.        value 7 with index 2
13.        value 10 with index 3
14. */

forEachIndexed memiliki dua argumen. Pertama adalah index yang merupakan indeks dari


tiap nilai. Kedua adalah value yang merupakan nilai tunggal yang dicakup oleh ranges itu
sendiri. Jika kita hanya ingin menggunakan argumen index, maka kita bisa mengubah
argumen value menjadi _ seperti berikut:
1. fun main() {
2.     val ranges = 1.rangeTo(10) step 3
3.     ranges.forEachIndexed { index, _ ->
4.         println("index $index")
5.     }
6. }
7. /*
8.    output :
9.        index 0
10.        index 1
11.        index 2
12.        index 3
13. */

Sebenarnya ini merupakan sebuah aturan di mana ketika argumen dari sebuah lambda
expression tidak digunakan, kita disarankan agar mengubahnya menjadi _ untuk
menggantikan nama dari argumen tersebut
Break dan Continue
Ketika melakukan perulangan, terkadang kita dihadapkan dengan data yang tak sesuai
harapan. Contoh, seperti berikut:

1. fun main() {
2.     val listOfInt = listOf(1, 2, 3, null, 5, null, 7)
3.     for (i in listOfInt) {
4.         print(i)
5.     }
6. }
7. /*
8.    output: 123null5null7
9. */

Proses perulangan pada kode di atas akan menghasilkan nilai null. Jika kita mengelola nilai
tersebut, ada potensi NullPointerException di sana. Lalu bagaimana kita melewatkan atau
menghentikan proses perulangan jika nilai yang dihasilkan bernilai null? Nah, di sini kita
bisa menggunakan Break dan Continue.

Continue digunakan untuk melewatkan proses iterasi dan lanjut dengan proses iterasi
berikutnya. Sementara itu, Break digunakan untuk menghentikan proses iterasi.

Berikut adalah contoh proses iterasi pada kode di atas. Kita akan coba melewatkannya jika
nilai yang dihasilkan adalah null.

1. fun main() {
2.     val listOfInt = listOf(1, 2, 3, null, 5, null, 7)
3.  
4.     for (i in listOfInt) {
5.         if (i == null) continue
6.         print(i)
7.     }
8. }
9. /*
10.    output: 12357
11. */

Pada kode di atas kita menggunakan kata kunci continue. Jika hasil


evaluasi expression yang diberikan bernilai true, maka proses iterasi akan dilewatkan dan
lanjut ke proses iterasi berikutnya.

Berikut adalah contoh jika kita ingin menghentikan proses iterasi ketika nilai yang dihasilkan
bernilai null.

1. fun main() {
2.     val listOfInt = listOf(1, 2, 3, null, 5, null, 7)
3.  
4.     for (i in listOfInt) {
5.         if (i == null) break
6.         print(i)
7.     }
8. }

Penggunaan break pada kode di atas akan langsung menghentikan proses iterasi jika


variabel i bernilai null.
Break dan Continue Labels
Pada Kotlin, sebuah expression dapat ditandai dengan sebuah label. Label pada Kotlin
memiliki sebuah identifier yang diikuti dengan tanda @. Contoh dari sebuah label
adalah foo@ atau bar@.

Untuk melabeli sebuah expression, kita cukup menempatkan label di depannya. Contohnya


seperti berikut:

1. fun main() {
2.     loop@ for (i in 1..10) {
3.         println("Outside Loop")
4.  
5.         for (j in 1..10) {
6.             println("Inside Loop")
7.             if ( j > 5) break@loop
8.         }
9.     }
10. }
11.  
12. /*
13.    output :
14.        Outside Loop
15.        Inside Loop
16.        Inside Loop
17.        Inside Loop
18.        Inside Loop
19.        Inside Loop
20.        Inside Loop
21. */

Pada kode diatas, break yang sudah ditandai dengan label akan dilompati ke titik awal
proses perulangan yang sudah ditandai dengan label. break akan menghentikan proses
perulangan terluar dari dalam proses perulangan, di mana break tersebut dipanggil.
Rangkuman dari Control Flow
Sudah bisa menentukan alur program yang akan dikembangkan? Yuk kita ulas lagi apa
yang sudah kita pelajari pada modul Control Flow:

 Untuk menghindari penggunaan konstan yang keliru, kita bisa memanfaatkan fitur
Enumeration untuk menyimpan kumpulan objek yang telah didefinisikan menjadi tipe
data konstanta.
 Jika memiliki beberapa ekspresi untuk menentukan hasil evaluasi, Anda bisa
menggunakan when expression. Karena sebuah expression, when dapat
mengembalikan nilai yang dapat ditampung pada sebuah variabel.
 Sama seperti when, if expression dapat mengembalikkan nilai yang dapat ditampung
pada sebuah variabel. Namun sedikit berbeda dengan when, if lebih cocok
digunakan jika ekspresi yang akan digunakan untuk dievaluasi hanya 1 (satu).
 Dalam mempelajari bahasa pemgrograman, Anda akan sering menjumpai istilah
expressions dan statement. 
 Jika ingin melakukan perulangan, ada beberapa cara yang dapat diterapkan yaitu:

o While
While bersifat Entry Controlled Loop. Artinya, kondisi yang diberikan akan
dievaluasi terlebih dahulu. Jika kondisi tersebut terpenuhi maka proses
perulangan akan dijalankan.
o Do While
Do While bersifat Exit Controlled Loop di mana proses perulangan akan
langsung dijalankan di awal. Jika telah selesai, barulah kondisi yang diberikan
akan dievaluasi.
o For Loop
For merupakan konsep perulangan pada blok yang sama selama hasil
evaluasi kondisi yang diberikan terpenuhi atau bernilai true. For
memanfaatkan tipe data Range untuk menentukan kondisi yang akan
dievaluasi.
 Saat menggunakan While dan Do While perhatikan infinite loop, yaitu kondisi di
mana proses perulangan berlangsung terus menerus sampai aplikasi menjadi crash.
 Saat menerapkan perulangan, kita bisa memanfaatkan kata
kunci break dan continue. Kedua kata kunci tersebut digunakan untuk menentukan
proses perulangan akan seperti apa di mana break digunakan untuk menghentikan
proses perulangan walaupun hasil evaluasi masih menghasil true dan continue
digunakan untuk melanjutkan proses perulangan selanjutnya.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Ikuti tutorial berikut untuk menginstal plugin EduTools pada IntelliJ IDEA:

 Menginstal EduTools pada IntelliJ IDEA.

Setelah EduTools berhasil diinstal, buka proyek latihan dengan mengikuti tutorial berikut:

 Mengerjakan Latihan pada Kelas Memulai Pemrograman dengan Kotlin.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda mempelajari macam - macam Control Flow yang ada pada Kotlin. Untuk menguji
pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu mengerjakan
beberapa latihan sederhana. Buka proyek latihan dan kerjakan semua latihan yang ada
pada modul Control Flow.
Data Class
Pada modul ini, kita akan mempelajari sebuah fitur menarik pada Kotlin, yaitu Data Class.

Kotlin mengenalkan konsep data class yang merupakan sebuah kelas sederhana yang bisa
berperan sebagai data container. Data class adalah sebuah kelas yang tidak memiliki logika
apapun dan juga tidak memiliki fungsionalitas lain selain menangani data.

Kenapa disebut dengan kelas sederhana? Seperti yang sudah kita ketahui, Kotlin
memungkinkan kita untuk menulis kode dengan ringkas dan lebih efisien. Dalam membuat
sebuah data class, kita tidak perlu menuliskan banyak kode yang seharusnya dibutuhkan
untuk mengelola sebuah data. Data class mampu menyediakan beberapa fungsionalitas
yang biasanya kita butuhkan untuk mengelola data hanya dengan sebuah keyword data.

1. data class User(val name : String, val age : Int)

Hanya dengan satu baris kode di atas, kompiler akan secara otomatis
menghasilkan constructor, toString(), equals(), hashCode(), copy() dan juga
fungsi componentN(). Tentunya ini jauh lebih mudah dan bersih dibandingkan kita harus
menuliskan banyak kode secara manual.

Beberapa hal yang perlu diperhatikan dalam membuat sebuah data class adalah:

 Konstruktor utama pada kelas tersebut harus memiliki setidaknya satu parameter;
 Semua konstruktor utama perlu dideklarasikan sebagai val atau var;
 Modifier dari sebuah data class tidak bisa abstract, open, sealed, atau inner.
Penggunaan Data Class
Sebelum kita masuk ke dalam pembahasan tentang apa saja yang bisa data class lakukan,
mari kita perhatikan dulu kode berikut:

1. class User(val name : String, val age : Int)

Kode di atas merupakan sebuah kelas yang umumnya digunakan untuk menampung
sebuah data. Kelas tersebut memiliki sebuah konstruktor yang berisi beberapa properti yang
bisa kita akses, baik itu create maupun read. Selanjutnya, perhatikan juga kelas berikut:

1. data class DataUser(val name : String, val age : Int)

Kelas hampir sama dengan sebelumnya, namun memiliki keyword data yang menandakan


bahwa kelas tersebut merupakan sebuah data class. Lalu, apakah perbedaan antara
keduanya? Untuk mengetahuinya, bukalah Intellij IDEA dan buat sebuah berkas Kotlin
dengan nama DataClasses.kt. Ketikkan kedua kelas tadi di dalamnya dan buat juga
fungsi main sebagai tempat di mana kita akan mencoba mengelola atau mengoperasikan
kedua kelas tersebut.

1. class User(val name : String, val age : Int)


2.  
3. data class DataUser(val name : String, val age : Int)
4.  
5. fun main(){
6.  
7. }

Untuk mengetahui perbedaan yang pertama, kita akan menggunakan fungsi println() untuk


menampilkan 2 buah objek yang akan dibuat dari kelas User dan DataUser. Tambahkan
kode berikut di dalam fungsi main():

1. val user = User("nrohmen", 17)


2. val dataUser = DataUser("nrohmen", 17)
3.  
4. println(user)
5. println(dataUser)

Jalankan fungsi main dan lihatlah hasil yang ditampilkan pada konsol:

oo.User@4d7e1886
DataUser(name=nrohmen, age=17)

Bisa kita perhatikan, bahwa objek user menghasilkan


teks oo.User@4d7e1886 dimana oo merupakan nama package tempat
kelas User berada. User adalah nama dari kelas itu sendiri,
dan @4d7e1886 adalah memory address dari kelas tersebut. Sedangkan,
objek dataUser menghasilkan teks DataUser(name=nrohmen, age=17), yaitu nama kelas
disertai dengan semua properti di dalamnya dan value dari properti tersebut. 

Dengan begitu, Anda bisa langsung mengetahui semua informasi dari dataUser hanya


dengan melihat value dari properti yang ada. Mengapa demikian? Karena seperti yang
sudah disampaikan sebelumnya,  data class akan secara otomatis menghasilkan
fungsi toString() di dalamnya. Tanpa data class, kita perlu membuat fungsi toString() secara
manual untuk mendapatkan informasi seperti yang diberikan oleh objek dataUser. Sebagai
contoh, untuk menampilkan informasi yang jelas dari objek user, maka kita perlu
menambahkan fungsi toString() seperti berikut:
1. class User(val name : String, val age : Int){
2.  
3.     override fun toString(): String {
4.         return "User(name=$name, age=$age)"
5.     }
6. }

Dengan menambahkan fungsi toString() seperti di atas, maka objek user akan bisa


menghasilkan teks yang sama dengan objek dataUser. Coba jalankan kembali
fungsi main().

User(name=nrohmen, age=17)
DataUser(name=nrohmen, age=17)
Selanjutnya, kelebihan lain dari data class adalah ia sudah memiliki fungsi equals() secara
otomatis. Maka jika Anda ingin melakukan komparasi antara 2 buah objek, lakukanlah
dengan mudah seperti contoh di bawah ini:

1. fun main(){
2.     val dataUser = DataUser("nrohmen", 17)
3.     val dataUser2 = DataUser("nrohmen", 17)
4.     val dataUser3 = DataUser("dimas", 24)
5.  
6.     println(dataUser.equals(dataUser2))
7.     println(dataUser.equals(dataUser3))
8.  
9. }

Konsol akan langsung memberi tahu apakah kedua objek tersebut sama atau tidak ketika
Anda menjalankan fungsi main():

true
false
Lain halnya jika kita melakukan komparasi pada 2 buah objek yang bukan dari data class.
Kita tidak bisa mendapatkan hasil yang akurat karena konsol akan selalu menghasilkan
nilai false. Sebagai contoh, perhatikanlah kode berikut:

1. fun main(){
2.     val user = User("nrohmen", 17)
3.     val user2 = User("nrohmen", 17)
4.     val user3 = User("dimas", 24)
5.  
6.     println(user.equals(user2))
7.     println(user.equals(user3))
8. }

Maka hasilnya akan sama saja, false semua:

false
false
Dan jika Anda menginginkan hasil yang akurat seperti pada data class, maka Anda perlu
membuat fungsi equals() secara manual:

1. class User(val name : String, val age : Int){


2.  
3.     override fun equals(other: Any?): Boolean {
4.         if (this === other) return true
5.         if (javaClass != other?.javaClass) return false
6.  
7.         other as User
8.  
9.         if (name != other.name) return false
10.         if (age != other.age) return false
11.  
12.         return true
13.     }
14.  
15.     override fun hashCode(): Int {
16.         var result = name.hashCode()
17.         result = 31 * result + age
18.         return result
19.     }
20. }

Anda perlu menuliskan beberapa boilerplate code di atas untuk mendapatkan hasil yang
sesuai. Belum lagi ketika Anda menambahkan fungsi equals(), Anda juga perlu
menambahkan fungsi hashCode().
Menyalin dan Memodifikasi Data Class
Data class juga memungkinkan kita untuk menyalin sebuah objek dengan sangat mudah
hanya dengan memanfaatkan fungsi copy() di dalamnya. Untuk mencobanya, buatlah objek
baru dari kelas DataUser seperti berikut:

1. fun main(){
2.     val dataUser = DataUser("nrohmen", 17)
3.     val dataUser2 = DataUser("nrohmen", 17)
4.     val dataUser3 = DataUser("dimas", 24)
5.     val dataUser4 = dataUser.copy()
6.  
7.     println(dataUser4)
8. }

Jalankan fungsi main() dan seharusnya nilai dari dataUser4 akan sama dengan nilai


dari dataUser. Menariknya, dengan fungsi copy() kita juga bisa memodifikasi objek tersebut
dengan nilai yang baru. Sebagai contoh, kita akan mengubah nilai dari
properti age menjadi 18. Cukup tuliskan kode seperti berikut:

1. val dataUser5 = dataUser.copy(age = 18)

Maka seharusnya konsol akan menampilkan teks berikut:

DataUser(name=nrohmen, age=18)

Tanpa data class, untuk melakukan tugas seperti ini kita memerlukan sebuah instance baru
untuk mengubah nilai dari suatu objek. Dengan demikian kita harus memodifikasi properti
yang kita maksud. Tugas ini akan berulang dan membuat kode yang kita tulis, jauh dari
paradigma clean code. 
Destructuring Declarations
Destructuring Declaration adalah proses memetakan objek menjadi sebuah variabel. Ini bisa
dengan mudah kita lakukan pada data class. Dengan fungsi componentN() yang ada pada
data class, kita bisa menguraikan sebuah objek menjadi beberapa properti yang dimilikinya.
Sebagai contoh, kita ingin menguraikan objek dataUser:

1. fun main(){
2.     val dataUser = DataUser("nrohmen", 17)
3.  
4.     val name = dataUser.component1()
5.     val age = dataUser.component2()
6.  
7.     println("My name is $name, I am $age years old")
8. }

Maka jika kode di atas dijalankan, konsol akan menampilkan teks berikut:

My name is nrohmen, I am 17 years old

Fungsi component1() dan component2() dihasilkan sesuai dengan jumlah properti yang ada


pada data class tersebut. Maka jika sebuah data class memiliki sejumlah N properti, maka
secara otomatis componentN() akan dihasilkan.

Kita juga dapat membuat beberapa variabel dari objek secara langsung dengan kode seperti
berikut:

1. fun main(){
2.     val dataUser = DataUser("nrohmen", 17)
3.     val (name, age) = dataUser
4.  
5.     println("My name is $name, I am $age years old")
6. }

Jika dijalankan, seharusnya konsol akan menampilkan hasil yang sama seperti kode
sebelumnya.

Kesimpulannya, seperti aspek - aspek lain dari Kotlin, data class bertujuan untuk
mengurangi jumlah kode boilerplate yang Anda tuliskan. Dan perlu diketahui bahwa data
class tidak hanya sekedar untuk mengelola properti yang ada di dalamnya. Ketika
mempunyai data yang sangat kompleks, kita juga bisa menerapkan sebuah behaviour di
dalam data class. Contoh sederhananya, kita bisa membuat fungsi di dalam data class
seperti berikut:

1. data class DataUser(val name : String, val age : Int){


2.     fun intro(){
3.         println("My name is $name, I am $age years old")
4.     }
5. }

Dan langsung mengaksesnya dari fungsi main():

1. fun main(){
2.     val dataUser = DataUser("nrohmen", 23)
3.     dataUser.intro()
4. }
Collections
Setelah berkenalan dan mempelajari data class, selanjutnya kita akan mencoba untuk
mempelajari collection. Bayangkan ketika kita ingin menyimpan dan memanipulasi sebuah
objek. Kita perlu sebuah komponen yang mampu menambahkan, menghapus, mencari,
bahkan mengurutkan sebuah data. Semua tugas itu bisa kita lakukan dengan bantuan
collection. Collections sendiri merupakan sebuah objek yang bisa menyimpan kumpulan
objek lain termasuk data class. Dengan collection kita bisa menyimpan banyak data
sekaligus. Di dalam collections terdapat beberapa objek turunan, di antaranya
adalah List, Set, dan Map. Mari kita pelajari satu per satu objek turunan tersebut.

List
Yang pertama adalah List. Dengan List kita dapat menyimpan banyak data menjadi satu
objek. Sebagai contoh, kita bisa membuat sebuah List yang berisi sekumpulan data angka,
karakter atau yang lainnya. Yang menarik, sebuah List tidak hanya bisa menyimpan data
dengan tipe yang sama. Namun juga bisa berisi bermacam - macam tipe data
seperti Int, String, Boolean atau yang lainnya. Cara penulisannya pun sangat mudah.
Perhatikan saja beberapa contoh kode berikut.

1. val numberList : List<Int> = listOf(1, 2, 3, 4, 5)

Kode di atas adalah contoh dari satu objek List yang berisi kumpulan data dengan tipe
Integer. Karena kompiler bisa mengetahui tipe data yang ada dalam sebuah objek List,
maka tak perlu kita menuliskannya secara eksplisit. Ini tentunya akan menghemat kode
yang kita ketikkan:

1. val numberList = listOf(1, 2, 3, 4, 5)


2. val charList = listOf('a', 'b', 'c')

Sedangkan untuk membuat List dengan tipe data yang berbeda, cukup masukkan saja data
tersebut seperti kode berikut:

1. val anyList = listOf('a', "Kotlin", 3, true)

Karena setiap objek pada Kotlin merupakan turunan dari kelas Any, maka variabel anyList
tersebut akan memiliki tipe data List<Any>. Jika kita tampilkan list di atas maka konsol akan
menampilkan:
[a, Kotlin, 3, true]

Bahkan kita pun bisa memasukkan sebuah data class ke dalam List tersebut:

1. val anyList = listOf('a', "Kotlin", 3, true, User())

Ketika bermain dengan sebuah List, tentunya ada saat di mana kita ingin mengakses posisi
tertentu dari List tersebut. Untuk melakukannya, kita bisa menggunakan
fungsi indexing seperti berikut:

1. println(anyList[3])

Perhatikan kode di atas. Fungsi indexing ditandai dengan tanda [ ]. Jika Anda mengira


bahwa konsol akan menampilkan angka 3, maka tebakan Anda kurang tepat. Karena dalam
sebuah List, indeks dimulai dari 0. Maka ketika kita akan mengakses data pada anyList yang
berada pada indeks ke-3, artinya data tersebut merupakan data pada posisi ke-4. Jadi data
yang akan ditampilkan pada konsol adalah true.

Lalu apa yang akan terjadi jika kita berusaha menampilkan item dari List yang berada di luar
dari ukuran List tersebut? Sebagai contoh, Anda ingin mengakses indeks ke-5 dari anyList:

1. println(anyList[5])

Hasilnya adalah eror! Kompiler akan memberitahukan bahwa perintah itu tidak bisa
dijalankan. Berikut pesan eror yang akan muncul:

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 5

Pesan di atas memberitahu kita bahwa List telah diakses dengan indeks ilegal. Ini akan
terjadi jika indeks yang kita inginkan negatif atau lebih besar dari atau sama dengan ukuran
List tersebut.

Informasi Tambahan:

Sejauh ini kita baru belajar menginisialisasikan atau mengakses data dari sebuah List. Pastinya Anda
bertanya, apakah bisa kita memanipulasi data pada List tersebut? Jawabannya tidak. Apa pasal? List
tersebut bersifat immutable alias tidak bisa diubah. Namun jangan khawatir. Kotlin standard library juga
menyediakan collection dengan tipe mutable. Artinya kita melakukan perubahan pada nilainya dengan
cara seperti menambah, menghapus, atau mengganti nilai yang sudah ada. Caranya pun cukup mudah.
Anda hanya perlu menggunakan fungsi mutableListOf seperti berikut:

1. val anyList = mutableListOf('a', "Kotlin", 3, true, User())

Dengan begitu, anyList sekarang merupakan sebuah List yang bersifat mutable dan kita bisa
memanipulasi data di dalamnya.

1. anyList.add('d') // menambah item di akhir list


2. anyList.add(1, "love") // menambah item pada indeks ke-1
3. anyList[3] = false // mengubah nilai item pada indeks ke-3
4. anyList.removeAt(1) // menghapus item User() berdasarkan index atau posisi nilai di dalam Array
Set
Selanjutnya kita akan membahas objek turunan yang kedua, yaitu Set. Set merupakan
sebuah collection yang hanya dapat menyimpan nilai yang unik. Ini akan berguna ketika
Anda menginginkan tidak ada data yang sama atau duplikasi dalam sebuah collection. Kita
bisa mendeklarasikan sebuah Set dengan fungsi setOf.

1. val integerSet = setOf(1, 2, 4, 2, 1, 5)

Perhatikan kode di atas. Di sana terdapat beberapa angka yang duplikat, yaitu
angka 1 dan 2. Silakan tampilkan pada konsol dan lihat hasilnya.

1. println(integerSet)
2.  
3. // Output: [1, 2, 4, 5]

Secara otomatis fungsi setOf akan membuang angka yang sama, sehingga hasilnya


adalah [1, 2, 4, 5]. Selain itu urutan pada Set bukanlah sesuatu yang penting, sehingga
apabila kita bandingkan dua buah Set yang memiliki nilai yang sama dan urutan yang
berbeda, akan tetap dianggap sama.

1. val setA = setOf(1, 2, 4, 2, 1, 5)


2. val setB = setOf(1, 2, 4, 5)
3. println(setA == setB)
4.  
5. // Output: true

Kita juga dapat melakukan pengecekan apakah sebuah nilai ada di dalam Set dengan
menggunakan kata kunci in.

1. print(5 in setA)
2.  
3. // Output: true

Kemudian ada juga fungsi union dan intersect untuk mengetahui gabungan dan irisan dari 2
(dua) buah Set. Sebagai contoh:

1. val setC = setOf(1, 5, 7)


2. val union = setA.union(setC)
3. val intersect = setA.intersect(setC)
4.  
5. println(union)
6. println(intersect)
7.  
8. // union: [1, 2, 4, 5, 7]
9. // intersect: [1, 5]

Informasi Tambahan:
Pada Mutable Set kita bisa menambah dan menghapus item namun tak bisa mengubah nilai seperti pada
List.

1. val mutableSet = mutableSetOf(1, 2, 4, 2, 1, 5)


2. //mutableSet[2] = 6 // tidak bisa mengubah set immutable
3. mutableSet.add(6) // menambah item di akhir set
4. mutableSet.remove(1) //menghapus item yang memiliki nilai 1
Map
Turunan yang ketiga adalah Map, yakni sebuah collection yang dapat menyimpan data
dengan format key-value. Perhatikan contoh berikut:

1. val capital = mapOf(


2.     "Jakarta" to "Indonesia",
3.     "London" to "England",
4.     "New Delhi" to "India"
5. )

String yang berada pada sebelah kiri dari kata kunci to adalah sebuah key, sedangkan yang
di sebelah kanan merupakan value-nya. Lalu untuk mengakses nilai dari Map tersebut, kita
bisa menggunakan key yang sudah dimasukkan. Misalnya, kita bisa menggunakan
key “Jakarta” untuk mendapatkan value “Indonesia”:

1. println(capital["Jakarta"])
2.  
3. // Output: Indonesia

Atau bisa juga menggunakan fungsi getValue():

1. println(capital.getValue("Jakarta"))
2.  
3. // Output: Indonesia

Hasilnya sama saja. Namun sebenarnya terdapat sebuah perbedaan antara keduanya. Saat
menggunakan simbol [ ] atau yang kita kenal dengan indexing, konsol akan menampilkan
hasil null saat key yang dicari tidak ada di dalam Map. Sedangkan saat kita
menggunakan getValue(), konsol akan memberikan sebuah Exception.

1. println(capital["Amsterdam"])
2.  
3. // Output: null
4.  
5.  
6. println(capital.getValue("Amsterdam"))
7.  
8. // Output: Exception in thread "main" java.util.NoSuchElementException: Key Amsterdam is
missing in the map.

Kita dapat menampilkan key apa saja yang ada di dalam Map dengan menggunakan
fungsi keys(). Fungsi ini akan mengembalikan nilai berupa Set karena key pada Map
haruslah unik.

1. val mapKeys = capital.keys


2.  
3. // mapKeys: [Jakarta, London, New Delhi]

Sedangkan untuk mengetahui nilai apa saja yang ada di dalam Map kita bisa menggunakan
fungsi values(). Fungsi ini akan mengembalikan collection sebagai tipe datanya.

1. val mapValues = capital.values


2.  
3. // mapValues: [Indonesia, England, India]

Informasi Tambahan:
Untuk menambahkan key-value ke dalam map, kita perlu memastikan bahwa map yang digunakan
adalah mutable. Mari kita ubah map capital yang sudah kita buat sebelumnya menjadi mutable.

1. val mutableCapital = capital.toMutableMap()

Selanjutnya kita bisa menambahkan key-value baru menggunakan fungsi put().

1. mutableCapital.put("Amsterdam", "Netherlands")
2. mutableCapital.put("Berlin", "Germany")

Namun perlu diperhatikan bahwa menggunakan mutable collection itu tidak disarankan. Apabila terdapat sebuah
mutable collection yang diubah oleh lebih dari 1 proses, hasil nya akan sulit untuk diprediksi.
Collections Operations
Selain memiliki beberapa turunan yang baru saja kita bahas, Collection juga mempunyai
beberapa fungsi operasi yang bisa kita gunakan untuk mengakses data di dalamnya.
Sekarang saatnya kita akan mempelajari fungsi-fungsi yang dimaksud. 

filter() dan filterNot()


Mari kita mulai dari fungsi filter() dan filterNot() terlebih dahulu. Kedua fungsi tersebut akan
menghasilkan list baru dari seleksi berdasarkan kondisi yang kita berikan. Sesuai dengan
namanya, untuk mem-filter atau menyaring suatu data dalam sebuah collection. Contohnya
dapat Anda lihat pada kode berikut:

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val evenList = numberList.filter { it % 2 == 0 }
3.  
4. // evenList: [2, 4, 6, 8, 10]

Pada kode di atas, kita telah menggunakan filter() untuk menyaring bilangan yang habis
dibagi 2 (dua) atau biasa disebut dengan bilangan genap. Selain itu kita juga dapat mem-
filter list berdasar hasil yang tak sesuai dengan kondisi yang diberikan. Caranya adalah
dengan menggunakan fungsi filterNot().

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val notEvenList = numberList.filterNot { it % 2 == 0 }
3.  
4. // notEvenList: [1, 3, 5, 7, 9]

Jadi, bisa disimpulkan bahwa filterNot() merupakan kebalikan dari filter().

map()
Fungsi yang akan sering dipakai berikutnya adalah map(). Fungsi ini akan membuat
collection baru sesuai perubahan yang akan kita lakukan dari collection sebelumnya. Kita
ambil contoh dari numberList yang sudah ada. Lalu kita buat collection baru yang isinya
adalah hasil kali 5 (lima) dari masing-masing item. Maka Anda bisa menggunakan kode
seperti berikut:

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val multipliedBy5 = numberList.map { it * 5 }
3.  
4. // multipliedBy5: [5, 10, 15, 20, 25, 30, 35, 40, 45, 50]

it pada kode di atas akan merepresentasikan masing masing item pada numberList.

count()
Fungsi count() dapat kita gunakan untuk menghitung jumlah item yang ada di dalam
collection. Kembali gunakan contoh numberList, kita akan menampilkan jumlah item yang
ada di dalamnya.

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. print(numberList.count())
3.  
4. // Output: 10

Cukup sederhana, bukan? Di dalam fungsi count() kita juga bisa menambahkan sebuah


parameter berupa lambda yang berisi sebuah kondisi. Sebagai contoh kali ini kita akan
menampilkan jumlah item pada numberList yang merupakan kelipatan dari 3.

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. print(numberList.count { it % 3 == 0 })
3.  
4. // Output: 3

Pada kode di atas konsol akan menampilkan 3 sebagai jumlah item yang merupakan
kelipatan 3, yaitu: 3, 6, dan 9.

find(), firstOrNull(), & lastOrNull()


Selanjutnya adalah fungsi yang digunakan untuk mencari item pada sebuah collection.
Untuk mencari item pertama yang sesuai dengan kondisi yang kita tentukan, kita bisa
menggunakan fungsi find(). Contoh, kita perlu mencari angka ganjil pertama
dari numberList maka kodenya akan seperti berikut:

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val firstOddNumber = numberList.find { it % 2 == 1 }
3. val firstOrNullNumber = numberList.firstOrNull { it % 2 == 3 }
4.  
5. // firstOddNumber: 1
6. // firstOrNullNumber: null

Fungsi find() ini memiliki cara kerja yang sama dengan fungsi firstOrNull(). Artinya, jika di


dalam collection tidak ditemukan data yang sesuai, maka fungsi akan mengembalikan
nilai null. Tidak seperti fungsi filter() atau map() yang akan melakukan iterasi terhadap
seluruh item, fungsi find() dan firstOrNull() ini akan langsung mengembalikan nilai ketika
kondisi terpenuhi. Kemudian jika Anda ingin mencari item terakhir, gunakan
fungsi lastOrNull().

first() & last()


Hampir sama seperti fungsi firstOrNull() dan lastOrNull(), fungsi first() dan last() digunakan
untuk menyaring item pertama atau terakhir dari sebuah collection. Kita juga bisa
menambahkan sebuah kondisi dengan parameter lambda. Namun perlu diperhatikan jika
kita menambahkan kondisi padahal kondisi tersebut tidak terpenuhi, apa
hasilnya? Exception! Lihat saja contohnya pada kode berikut:

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val moreThan10 = numberList.first { it > 10 }
3. print(moreThan10)
4.  
5. // Output: Exception in thread "main" java.util.NoSuchElementException: Collection
contains no element matching the predicate.
sum()
Mungkin Anda sudah tahu fungsi ini. Fungsi sum() khusus hanya bisa digunakan untuk
collection yang bertipe angka. Fungsi ini akan menjumlahkan setiap data yang ada pada
collection.

1. val numberList = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val total = numberList.sum()
3.  
4. // total: 55

sorted()
sorted() digunakan untuk mengurutkan item yang ada di dalam collection. Secara default
fungsi sorted() ini akan mengurutkan data secara ascending. Perhatikan kode di bawah ini:

1. val kotlinChar = listOf('k', 'o', 't', 'l', 'i', 'n')


2. val ascendingSort = kotlinChar.sorted()
3. println(ascendingSort)
4.  
5. // ascendingSort: [i, k, l, n, o, t]

Sedangkan untuk sorting secara descending, kita dapat menggunakan


fungsi sortedDescending().

1. val kotlinChar = listOf('k', 'o', 't', 'l', 'i', 'n')


2. val descendingSort = kotlinChar.sortedDescending()
3. println(descendingSort)
4.  
5. // descendingSort: [t, o, n, l, k, i]
Sequences
Tiga (3) jenis collection yang sudah kita pelajari sebelumnya merupakan jenis collection
yang menjalankan eager evaluation. Berbeda dengan itu, Sequence merupakan collection
yang bisa dikategorikan ke dalam lazy evaluation. Jika eager evaluation mengevaluasi
seluruh item yang ada pada collection [5], lazy evaluation hanya akan mengevaluasi  item
jika benar-benar diperlukan [6]. Kita ambil contoh.. Misalnya Anda mempunyai collection
dengan jumlah data 10 atau 100 , itu mungkin tidak akan memberi pengaruh besar terhadap
performa program. Namun apa yang terjadi jika data yang ada pada collection mencapai
ratusan ribu atau jutaan? Waktu proses pada sebuah program akan bertambah signifikan
jika semua datanya harus dievaluasi. 

1. fun main() {
2.     val list = (1..1000000).toList()
3.     list.filter { it % 5 == 0 }.map { it * 2 }.forEach { println(it) }
4. }

Pada contoh kode di atas, kita memiliki data collection sejumlah 1 juta item, kemudian
masing-masing data akan disaring berdasarkan angka yang merupakan kelipatan 5 lalu
dikalikan 2 dan akhirnya ditampilkan pada konsol. Dengan eager evaluation atau dikenal
dengan horizontal evaluation, list akan menyelesaikan proses filter terhadap 1 juta data baru
kemudian melakukan mapping data sampai akhirnya ditampilkan pada konsol.

Untuk menerapkan lazy  atau vertical evaluation maka kita perlu mengubah list menjadi


Sequence. Caranya sangat sederhana, yaitu dengan memanggil fungsi asSequence().

1. fun main() {
2.     val list = (1..1000000).toList()
3.     list.asSequence().filter { it % 5 == 0 }.map { it + it }.forEach { println(it) }
4. }

Dengan sequence, operasi akan dilakukan secara vertikal atau per item, misalnya dimulai
angka 1. Karena tidak memenuhi kondisi pada filter maka operasi tidak akan dilanjutkan
ke map(). Sampai dengan iterasi ke-5 akan dilakukan mapping, angka 5 akan dikalikan 2
dan ditampilkan pada konsol, setelah itu akan dilanjutkan ke iterasi berikutnya sampai
dengan iterasi ke-1 juta.

Untuk membuat objek Sequence, kita bisa menggunakan fungsi yang tersedia
pada standard library yaitu generateSequence().

1. val sequenceNumber = generateSequence(1) { it + 1 }

Pada kode di atas kita telah membuat variabel sequenceNumber dengan menggunakan


fungsi generateSequence() Fungsi ini memiliki 2 parameter. Parameter pertama adalah item
pertama yang ada di dalam collection. Parameter kedua adalah lambda expression berisi
perubahan pada masing-masing item. Pada contoh kode tadi, setiap item merupakan item
sebelumnya ditambah 1. Fungsi generateSequence() akan membuat collection sequence
secara tak terbatas. Alhasil, kita perlu menambahkan fungsi take() supaya program tidak
mengalami infinite loop.

1. fun main() {
2.     val sequenceNumber = generateSequence(1) { it + 1 }
3.     sequenceNumber.take(5).forEach { print("$it ") }
4. }
5. // Output: 1 2 3 4 5
Yosh! Anda sudah selesai dengan materi beberapa tipe Classes dan Collections. Sekarang
kita akan merangkum apa saja yang sudah Anda pelajari.

 Data class merupakan sebuah kelas sederhana yang bisa berperan sebagai data
container.
 Data class adalah kelas yang bisa disebut spesial karena kita tidak perlu
menentukkan nilai dari fungsi toString(), equals(), dan hashCode() ketika
digunakan.
 Data class juga menyediakan fungsi copy() yang memudahkan kita untuk menyalin
instance dari data class yang sudah dibuat.
 Collections merupakan sebuah objek yang bisa menyimpan kumpulan objek lain
termasuk data class. Berikut adalah beberapa jenis Collections yang sudah
dipelajari:
o List
Dengan List kita dapat menyimpan banyak data menjadi satu objek. Sebagai
contoh, kita bisa membuat sebuah List yang berisi sekumpulan data angka,
karakter atau yang lainnya.
o Set
Set merupakan sebuah collection yang hanya dapat menyimpan nilai yang
unik. Ini akan berguna ketika Anda menginginkan tidak ada data yang sama
atau duplikasi dalam sebuah collection. Kita bisa mendeklarasikan sebuah
Set dengan fungsi setOf.
o Map
Map, yakni sebuah collection yang dapat menyimpan data dengan format
key-value.
o Sequences
Sequence merupakan collection yang bisa dikategorikan ke dalam lazy
evaluation. Jika eager evaluation mengevaluasi seluruh item yang ada pada
collection, lazy evaluation hanya akan mengevaluasi item jika benar-benar
diperlukan.
 Collection juga mempunyai beberapa fungsi operasi yang bisa kita gunakan untuk
mengakses data di dalamnya dengan cara yang mudah, beberapa diantaranya
adalah:
o filter() dan filterNot()
Kedua fungsi tersebut akan menghasilkan list baru dari seleksi berdasarkan
kondisi yang kita berikan. Sesuai dengan namanya, untuk memfilter atau
menyaring suatu data dalam sebuah collection.
o map()
Fungsi ini akan membuat collection baru sesuai perubahan yang akan kita
lakukan dari collection sebelumnya.
o count()
Fungsi count() dapat kita gunakan untuk menghitung jumlah item yang ada di
dalam collection.
o find(), firstOrNull(), & lastOrNull()
Selanjutnya adalah fungsi yang digunakan untuk mencari item pada sebuah
collection. Untuk mencari item pertama yang sesuai dengan kondisi yang kita
tentukan, kita bisa menggunakan fungsi find().
o first() & last()
Hampir sama seperti fungsi firstOrNull() dan lastOrNull(),
fungsi first() dan last() digunakan untuk menyaring item pertama atau
terakhir dari sebuah collection. Kita juga bisa menambahkan sebuah kondisi
dengan parameter lambda.
o sum()
Mungkin Anda sudah tahu fungsi ini. Fungsi sum() khusus hanya bisa
digunakan untuk collection yang bertipe angka. Fungsi ini akan
menjumlahkan setiap data yang ada pada collection.
o sorted()
sorted() digunakan untuk mengurutkan item yang ada di dalam collection.
Secara default fungsi sorted() ini akan mengurutkan data secara ascending.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari apa itu data class, bagaimana cara menggunakannya, serta
mempelajari beberapa macam Collection pada Kotlin.

Untuk menguji pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu
mengerjakan beberapa latihan sederhana. Buka kembali proyek latihan dan kerjakan semua
latihan yang ada pada modul Data Classes dan Collections.
Functional Programming
Seperti yang sudah disampaikan di awal akademi. Kotlin adalah sebuah multiparadigm
programming language. Artinya selain merupakan bahasa pemrograman berorientasi objek,
dalam penulisan sintaksnya Kotlin menggunakan gaya functional programming.

Untuk mengawalinya, perhatikan kode berikut:

1. val list = getListUser()


2.  
3. fun getUsername(): List<String>{
4.     val name = mutableListOf<String>()
5.     for (user in list){
6.         name.add(user.name)
7.     }
8.     return name
9. }

Kode di atas biasanya kita tuliskan untuk mendapatkan nilai tertentu dari sebuah list. Karena
kode pada Kotlin bisa dituliskan dengan gaya fungsional, maka kode di atas cukup dituliskan
seperti berikut:

1. fun getUsername(): List<String>{


2.     return list.map {
3.         it.name
4.     }
5. }

Itu adalah salah satu contoh kenapa Kotlin termasuk ke dalam functional programming.
Untuk lebih memahaminya, kita akan belajar tentang fitur atau komponen pada Kotlin yang
terkait dengan functional programming. Anda akan mempelajari lebih detail tentang anatomi
dari sebuah fungsi pada Kotlin, bagaimana fungsi Kotlin berperilaku, lambda, higher-
order function dan konsep fungsional lainnya.
Anatomi Function
Pada modul pengenalan, kita sudah belajar tentang function (fungsi). Mulai dari bagaimana
cara kita mendeklarasinya, menentukan apakah fungsi tersebut dapat mengembalikan nilai,
serta melampirkan sebuah argumen ketika fungsi tersebut digunakan. 

Pada modul ini kita akan membahas lebih dalam tentang bagian apa saja yang terdapat
dalam sebuah fungsi. Pada dasarnya sebuah fungsi memiliki 2 (dua) bagian utama
yaitu function header dan function body.
Function Header
Function header adalah bagian yang merupakan konstruksi dari sebuah fungsi untuk
menentukan perilakunya akan seperti apa. Di dalam function header terdapat visibility
modifier, kata kunci fun, nama, daftar parameter dan nilai kembalian dari fungsi tersebut.

Visibility Modifier
Visibility modifier atau tingkatan akses merupakan bagian spesifik dari sebuah bahasa
pemrograman yang ditujukan untuk mengatur bagaimana hak akses dari sebuah kelas,
fungsi, properti dan variabel.

Secara default ketika kita membuat sebuah fungsi baru, ia akan memiliki modifier public.


Artinya fungsi tersebut dapat diakses dari luar kelas. Sedangkan contoh pada ilustrasi di
atas adalah sebuah fungsi yang memiliki modifier private. Maka akses dari fungsi tersebut
terbatas hanya untuk kelas di mana fungsi tersebut dideklarasi.

Terdapat beberapa visibility modifier yang akan kita pelajari bersama pada modul Object


Orientation Programming nanti.

Function Name
Setelah visibility modifier, selanjutnya adalah kata kunci fun . Ini digunakan untuk
menandakan jika baris kode tersebut merupakan sebuah fungsi yang kemudian diikuti oleh
nama dari fungsi tersebut.
Nama dari sebuah fungsi merupakan sebuah identifier yang akan memudahkan kita untuk
menggunakan fungsi tersebut. Perlu diketahui bahwa kita tidak bisa memberikan nama yang
sama untuk beberapa fungsi.

Untuk penamaan dari sebuah fungsi sendiri menggunakan format penulisan camelCase.


Nama dari fungsi tersebut diawali dengan huruf kecil dan huruf besar untuk kata berikutnya.
Ini merupakan standar penulisan resmi yang sudah ditentukan.

Function Parameter
Ketika kita mendeklarasikan sebuah fungsi baru, kita bisa atau tanpa menetapkan
parameter tersebut. Parameter adalah data atau nilai yang disematkan ketika fungsi tersebut
dipanggil. Nantinya parameter akan diproses di dalam fungsi tersebut. Kita bisa
melampirkan nilai konstan atau variabel untuk sebuah fungsi ketika ia dipanggil.
Parameter dari sebuah fungsi terdiri dari nama dan tipe dari parameter itu sendiri. Ia
digunakan untuk menentukan nilai atau variabel apa yang dapat dilampirkan. Setiap
parameter dipisahkan oleh tanda (,). Catatan: setiap parameter bersifat read-only, sehingga
kita hanya diijinkan untuk menggunakan nilainya untuk diproduksi.

Function return type


Terakhir adalah return type. Setiap fungsi yang kita deklarasi sejatinya akan selalu
mengembalikan dan nilai yang akan dikembalikan bisa kita gunakan untuk keperluan lain.
Misalnya untuk dijadikan sebagai argumen untuk fungsi lainnya.

Lalu apakah tipe ini harus ditentukan setiap deklarasi sebuah fungsi? Tentu tidak. Jika kita
tidak menentukan return type, secara implisit fungsi tersebut akan mengembalikan nilai
dengan tipe Unit, yaitu return type yang tidak mengembalikan nilai signifikan.
Function Body
Setelah function header, selanjutnya adalah function body, yang ditandai dengan curly
braces. Di dalamnya kita akan menempatkan sebuah logika kode baik itu
sebuah expression atau statement. Seperti yang dijelaskan pada modul pengenalan, jika
kita menetapkan sebuah fungsi dapat mengembalikan nilai, maka kita wajib menambah
sebuah baris kode yang diawali dengan kata kunci return yang diikuti oleh expression untuk
menetapkan nilai yang akan dikembalikan.

Sebaliknya, jika kita tidak ingin mengembalikan nilai, kita tidak perlu menambahkannya
seperti yang disebutkan di atas.

Setiap fungsi yang kita deklarasikan, memiliki ruang lingkup tersendiri. Contohnya saat kita
mendeklarasi sebuah variabel di dalamnya, variabel tersebut akan menjadi variabel lokal
untuk fungsi itu sendiri. Dengan demikian, variabel tersebut tidak dapat diakses dari fungsi
lainnya meskipun berada di dalam satu kelas yang sama.
Di beberapa kasus pembuatannya, sebuah fungsi bisa menjadi cukup kompleks dengan
banyaknya parameter. Alhasil saat ingin memanggil fungsi tersebut, kita harus menghafal
posisi dari parameter agar tidak salah dalam melampirkan sebuah argumen.

Named Argument
Untuk mengatasi hal ini Kotlin menawarkan sebuah cara. Dengan ini, kita tak perlu lagi
menghafal posisi dari sebuah parameter. Karena ketika sebuah fungsi dipanggil, kita bisa
menentukan argumen dari parameter mana yang ingin dilampirkan dengan memanggil
nama dari parameter tersebut. Misalnya kita mempunyai sebuah fungsi seperti berikut:

1. fun getFullName(first: String, middle: String, last: String): String {


2.     return "$first $middle $last"
3. }

Nah, dengan memanfaatkan named argument, kita bisa menuliskannya seperti di bawah ini:

1. fun main() {
2.     val fullName = getFullName(first = "Kotlin" , middle = " is ", last = "Awesome")
3.     print(fullName)
4. }
5.  
6. fun getFullName(first: String, middle: String, last: String): String {
7.     return "$first $middle $last"
8. }

Menariknya lagi, kita bisa mengubah posisi dari argumen ketika dilampirkan, misalnya
seperti ini:

1. fun main() {
2.     val fullName = getFullName(middle = " is " , first = "Kotlin", last = "Awesome")
3.     print(fullName)
4. }
5.  
6. fun getFullName(first: String, middle: String, last: String): String {
7.     return "$first $middle $last"
8. }

Dengan cara seperti di atas, kita tidak perlu lagi menghafal posisi dari parameter jika ingin
melampirkan sebuah argumen. Cukup hafalkan nama dari parameter tersebut.

Default Argument
Tidak sampai di situ, Kotlin juga memungkinkan kita untuk menentukan nilai default dari
sebuah parameter. Jika kita melewatkan argumen untuk dilampirkan, maka
nilai default tersebut lah yang akan digunakan.

Untuk menambahkan nilai default itu sendiri pun cukup mudah, yaitu dengan cara
menempatkannya langsung tepat di samping dari parameter seperti halnya ketika ingin
menginisialisasikan sebuah nilai untuk variabel. Contohnya seperti berikut:

1. fun getFullName(
2.         first: String = "Kotlin",
3.         middle: String = " is ",
4.         last: String = "Awesome"): String {
5.     return "$first $middle $last"
6. }
Kita bisa memanggil fungsi di atas seperti biasanya. Tetapi karena parameternya sudah
memiliki nilai, maka argumen untuk fungsi tersebut bisa dilewatkan ketika dipanggil.

1. fun main() {
2.     val fullName = getFullName()
3.     print(fullName)
4. }
5.  
6. fun getFullName(
7.         first: String = "Kotlin",
8.         middle: String = " is ",
9.         last: String = "Awesome"): String {
10.     return "$first $middle $last"
11. }
12.  
13. /*
14.    output : Kotlin is Awesome
15. */

Menarik bukan? Ketika kita telah menetapkan nilai default, kita tak perlu khawatir saat lupa
melampirkan sebuah argumen. Tentunya ini menghindari kita dari eror. Meskipun begitu,
kita tetap bisa melampirkan sebuah argumen. Contohnya seperti berikut:

1. fun main() {
2.     val fullName = getFullName(first = "Dicoding")
3.     print(fullName)
4. }
5.  
6. fun getFullName(
7.         first: String = "Kotlin",
8.         middle: String = " is ",
9.         last: String = "Awesome"): String {
10.     return "$first $middle $last"
11. }
12.  
13. /*
14.     output : Dicoding is Awesome
15.  */

Dengan memanfaatkan named dan default argument, kode yang kita tulis lebih mudah
dibaca. Ini pun dapat membantu kita dalam menggunakan fungsi yang cukup kompleks.
Vararg (Variable Argument)
Selain named dan default argument, Kotlin juga memiliki vararg (variable argument).
Dengan menggunakan kata kunci vararg kita juga bisa menyederhanakan beberapa
parameter yang memiliki tipe data yang sama menjadi parameter tunggal. 

Dengan  vararg sebuah fungsi dapat memiliki jumlah parameter berdasarkan jumlah


argumen yang kita masukkan ketika fungsi tersebut dipanggil. Contoh dari
penggunaan vararg adalah sebagai berikut:

1. fun sumNumbers(vararg number: Int): Int {


2.     return number.sum()
3. }

Bisa kita perhatikan pada contoh kode di atas bahwa kata kunci vararg ditempatkan
sebelum nama parameter. Ketika ingin memanggil fungsi tersebut, kita bisa melampirkan
beberapa argumen, misal seperti berikut:

1. fun main() {
2.     val number = sumNumbers(10, 20, 30, 40)
3.     print(number)
4. }
5.  
6. fun sumNumbers(vararg number: Int): Int {
7.     return number.sum()
8. }
9.  
10. /*
11.    output : 100
12. */

Selain itu kita juga bisa menerapkan Generic untuk tipe parameter ketika parameter tersebut
ditentukan dengan vararg. Contohnya seperti berikut:

1. fun <T> asList(vararg input: T): List<T> {


2.     val result = ArrayList<T>()
3.     for (item in input)
4.         result.add(item)
5.     return result
6. }

Ketika sebuah parameter ditentukan dengan vararg, pada dasarnya semua argumen yang
dilampirkan, ditampung di dalam sebuah Array <out T>. Contohnya bisa kita lihat pada
contoh kode di atas. 

Karena pada dasarnya adalah sebuah Array, maka kita bisa memanggil fungsi atau properti
yang tersedia pada kelas Array dari dalam fungsi tersebut. Misal properti size:

1. fun main() {
2.     val number = getNumberSize(10, 20, 30, 40, 50)
3.     print(number)
4. }
5.  
6. fun getNumberSize(vararg number: Int): Int {
7.     return number.size
8. }
9.  
10. /*
11.    output : 5
12. */
Lalu kapan kita membutuhkan vararg?  Ketika sebuah fungsi yang menggunakannya tidak
mengetahui jumlah argumen yang akan disematkan ketika fungsi tersebut dipanggil. Contoh
penerapan bisa kita liat pada fungsi String.format(),  di mana fungsi tersebut terdapat
parameter yang ditandai dengan vararg dan dapat disematkan beberapa argumen tanpa
harus tahu batasannya.

Aturan pada Vararg Arguments


Dalam penggunaannya, terdapat aturan yang perlu kita ketahui. Pertama, di dalam sebuah
fungsi, tidak diizinkan untuk memiliki 2 (dua) parameter bertanda vararg.

1. fun sumNumbers(vararg number: Int, vararg number2: Int)

Ketika kode di atas dijalankan, proses kompilasi akan gagal dengan log eror sebagai berikut:
Kotlin: Multiple vararg-parameters are prohibited

Selanjutnya jika kita ingin menambahkan parameter baru tanpa kata kunci vararg, parameter
yang ditandai dengan vararg sebaiknya berada pada posisi pertama. Contohnya seperti
berikut:

1. fun sets(vararg number: Int, name: String): Int {


2.     ...
3. }

Lalu bagaimana jika kita ingin menempatkan parameter yang ditandai vararg bukan pada
posisi pertama? Kita bisa mendifisikannya secara langsung. Namun berbeda saat fungsi
tersebut dipanggil di mana kita harus menggunakan named argument saat ingin
melampirkan argumen untuk parameter yang tidak ditandai dengan vararg. Contohnya
seperti berikut:

1. fun main() {
2.     sets(10, 10, name = "Kotlin")
3. }
4.  
5. fun sets(vararg number: Int, name: String): Int {
6.     ...
7. }

Kenapa demikian? Saat kita tidak menandai argumen tersebut untuk parameter yang mana,
kompiler akan menetapkan jika argumen tersebut untuk parameter yang ditandai
dengan vararg.

Vararg vs Array<T>
Karena semua argumen ditampung di dalam sebuah Array, maka akan muncul pertanyaan,
"Apa bedanya ketika kita menggunakan Array<T> sebagai tipe parameter?" Misal seperti
berikut:

1. fun sets(number: Array<Int>){


2.     ...
3. }
Dari sini kita bisa lihat langsung letak perbedaannya di mana. Ketika fungsi di atas dipanggil,
fungsi tersebut membutuhkan argumen berupa nilai yang sudah berbentuk Array seperti
berikut:

1. fun main() {
2.     val number = arrayOf(10, 20, 30, 40)
3.     sets(number)
4. }
5.  
6. fun sets(number: Array<Int>) {
7.     ...
8. }

Berbeda ketika kita menggunakan vararg. Kita bisa memasukkan argumen satu persatu.


Lalu apakah bisa kita memasukkan nilai yang sudah berbentuk Array sebagai argumen
untuk parameter yang ditandai dengan vararg?

Tentu bisa! Dengan memanfaatkan spread operator (*) seperti berikut:

1. fun main() {
2.     val number = intArrayOf(10, 20, 30, 40)
3.     sets(10, 20, 20, *number , 10)
4. }
5.  
6. fun sets(vararg number: Int): Int {
7.     ...
8. }

Dalam penggunaannya, spread operator ditempatkan sebelum nama variabel yang ingin


dilampirkan.
Kotlin memungkinkan kita untuk menambahkan sebuah fungsi baru pada sebuah kelas
tanpa harus mewarisi kelas tersebut. Misal kita ingin menambahkan fungsi baru untuk kelas
Int, maka kita akan menuliskannya seperti berikut:

1. class NewInt : Int(){


2.     fun printInt(){
3.         println("value $this")
4.     }
5. }

Ketika dijalankan, kode di atas akan gagal dikompilasi, kenapa? Karena


kelas Int bersifat final, sehingga tidak memungkinkan untuk mewarisi kelas tersebut. Untuk
itu, kita bisa melakukannya dengan deklarasi khusus yang disebut dengan Extensions. 

Kotlin mendukung 2 (dua) extension yang dapat digunakan, yaitu Extension


Functions dan Extension Properties. Jika extension functions digunakan untuk
menambahkan fungsi baru, extension properties tentunya digunakan untuk menambahkan
sebuah properti baru.

Extension Functions
Untuk mendeklarasikan sebuah extension functions, kita perlu menentukan terlebih
dahulu receiver type, kemudian nama dari fungsi tersebut yang mana keduanya dipisahkan
oleh titik (.). Contohnya, seperti berikut:

1. fun Int.printInt() {
2.     print("value $this")
3. }

Bisa kita perhatikan, kelas Int pada kode di atas digunakan sebagai receiver type,


sedangkan kata kunci this adalah receiver type yang bertindak sebagai objeknya. Nilai dari
objek tersebut bisa digunakan di dalam extension yang sudah dibuat. 

Untuk memanggil extension functions di atas, lakukan dengan cara seperti berikut:

1. fun main() {
2.     10.printInt()
3. }
4.  
5. fun Int.printInt() {
6.     print("value $this")
7. }
8.  
9. /*
10.    output : value 10
11. */

Kita juga bisa menetapkan jika extension functions tersebut dapat mengembalikan nilai,
deklarasinya pun sama halnya seperti fungsi pada umumnya. Contohnya seperti berikut:

1. fun main() {
2.     println(10.plusThree())
3. }
4.  
5. fun Int.plusThree(): Int {
6.     return this + 3
7. }
8.  
9. /*
10.    output : 13
11. */

Extension Properties
Selanjutnya adalah extension properties. Seperti yang disebutkan di awal, Kotlin juga
mendukung extension untuk menambah sebuah properti baru pada sebuah kelas tanpa
harus menyentuh kode di dalam kelas tersebut.

Deklarasinya pun sama seperti extension functions. Kita terlebih dahulu


menentukan receiver type kemudian nama dari properti tersebut. Contoh seperti berikut:

1. val Int.slice: Int


2.     get() = this / 2

Untuk memanggil extension di atas, lakukan dengan cara berikut:

1. fun main() {
2.     println(10.slice)
3. }
4.  
5. val Int.slice: Int
6.     get() = this / 2
7.  
8. /*
9.    output : 5
10. */

Yang perlu diketahui, extension tidak benar-benar mengubah sebuah kelas dengan


menambahkan sebuah fungsi atau properti baru. Ini karena extension memiliki hubungan
langsung dengan kelas yang ingin diperluas fungsionalitasnya. Sehingga extension
properties hanya bisa dideklarasikan dengan cara menyediakan getter atau setter secara
eksplisit.
Nullable Receiver
Menariknya, kita bisa juga mendeklarasikan sebuah extension dengan nullable receiver
type. Alhasil, extension tersebut bisa dipanggil pada objek yang bahkan nilainya null.

1. val Int?.slice: Int


2.     get() = if (this == null) 0 else this.div(2)

If expression pada contoh di atas adalah untuk memeriksa apakah receiver object-nya


bernilai null. Jika tidak bernilai null, maka receiver object tersebut akan secara otomatis di-
casting menjadi tipe non-null, sehingga kita bisa menggunakan nilainya.

Selain menggunakan if expression, kita juga bisa menggunakan elvis operator. Misalnya
seperti berikut:

1. val Int?.slice: Int


2.     get() = this?.div(2) ?: 0

Untuk memanggilnya pun sama seperti extension properties sebelumnya.

1. fun main() {
2.     val value: Int? = null
3.  
4.     println(value.slice)
5. }
6.  
7. val Int?.slice: Int
8.     get() = this?.div(2) ?: 0
9.  
10. /*
11.    output : 0
12. */

Lalu kapan kita membutuhkannya? Tentunya jika kita mempunyai sebuah objek yang
bernilai null. Saat kita tidak menetapkannya dengan nullable receiver type, maka kita perlu
memeriksa apakah objek tersebut bernilai null atau tidak? Bisa juga dengan menggunakan
operator safe call setiap kali extension tersebut dipanggil. Contohnya seperti berikut:

1. fun main() {
2.     val value: Int? = null
3.     val value1: Int? = null
4.  
5.     println(value?.slice)
6.     println(value1?.slice)
7. }
8.  
9. val Int.slice: Int
10.     get() = this.div(2)
11.  
12. /*
13.    output : null
14.             null
15.  
16. */

Kita juga bisa menentukan nilai dari receiver object jika bernilai null. Sehingga kita tidak
perlu lagi menggunakan operator safe call ketika ingin memanggil extension tersebut.

1. fun main() {
2.     val value: Int? = null
3.     val value1: Int? = null
4.  
5.     println(value.slice)
6.     println(value1.slice)
7. }
8.  
9. val Int?.slice: Int
10.     get() = this?.div(2) ?: 0
11.  
12. /*
13.    output : 0
14.             0
15.  
16. */
Function Type
Pada modul selanjutnya, kita akan belajar tentang lambda dan higher-order function,
namun sebelum itu kita perlu tahu terlebih dahulu apa itu funcion type. Seperti namanya,
Anda dapat membuat sebuah fungsi menjadi tipe data. Menarik kan?

Kotlin sendiri menggunakan function type untuk tipe deklarasi yang berhubungan dengan
sebuah fungsi. Dalam penggunaannya, terdapat beberapa tanda yang berhubungan dengan
sebuah fungsi seperti jumlah dan tipe parameter serta tipe kembalian.

1. (Int, Int) -> String

Setiap function type memiliki tanda kurung . Di dalamnya terdapat sebuah parameter dan
jumlah tipe yang menandakan jumlah parameter dari fungsi tersebut. Pada contoh di atas,
fungsi tersebut memiliki 2 (dua) parameter dengan tipe Int dan memiliki tipe kembalian
String. Ketika kita tidak ingin fungsi tersebut mengembalikan nilai, kita bisa
menggunakan Unit. Berbeda dengan fungsi pada umumnya, jika menggunakan tipe
kembalian Unit, kita tidak wajib menuliskannya.

Ketika kita mempunyai beberapa fungsi yang memiliki function type yang sama, kita bisa
menyederhanakannya. Bagaimana caranya? Manfaatkan kata kunci typealias untuk
memberikan nama alternatif dari sebuah function type seperti berikut:

1. typealias Arithmetic = (Int, Int) -> Int


2.  
3. val sum: Arithmetic = { valueA, valueB -> valueA + valueB }
4.  
5. val multiply: Arithmetic = { valueA, valueB -> valueA * valueB }

typealias cocok digunakan ketika kita mempunyai sebuah function type yang panjang.
Dengannya, kita bisa memberikan nama untuk sebuah function type dan menggunakannya
sebagai tipe untuk fungsi lainnya. Nah, sekarang kalau dilihat sudah benar-benar mirip
seperti tipe data kan? Mantap!

Untuk membuat instance dari sebuah function type, terdapat beberapa cara. Salah satunya
dengan lambda expression yang sudah kita bahas pada modul sebelumnya. Sedangkan
untuk menggunakan instance-nya, kita bisa memanfaatkan operator invoke() seperti berikut:

1. val sumResult = sum.invoke(10, 10)


2. val multiplyResult = multiply.invoke(20, 20)

Atau kita bisa menuliskannya secara langsung dengan menghilangkan operator invoke():

1. val sumResult = sum(10, 10)


2. val multiplyResult = multiply(20, 20)

Kita juga bisa menandai function type sebagai nullable dengan menempatkannya di dalam


tanda kurung dan diakhiri dengan safe call seperti berikut:

1. typealias Arithmetic = ((Int, Int) -> Int)?


2. val sum: Arithmetic = { valueA, valueB -> valueA + valueB }

Berikut adalah contoh penggunaan function type yang ditandai sebagai nullable:

1. sum?.invoke(10, 20)
Lambda
Lambda expression, biasa disebut dengan anonymous function atau function literal adalah
fitur yang cukup populer sampai sekarang dalam dunia functional programming. Bisa disebut
sebagai anonymous karena lambda tidak memiliki sebuah nama seperti halnya sebuah
fungsi pada umumnya. Karena merupakan sebuah fungsi, lambda juga dapat memiliki
daftar parameter, body dan return type. Istilah lambda sendiri berasal dari istilah
akademis lambda calculus yang digunakan untuk menggambarkan proses komputasi.

Sebelum mempelajarinya lebih dalam, ada baiknya jika kita tahu beberapa karakteristik dari
lambda berikut:

 Dalam menggunakan lambda, kita tidak perlu mendeklarasi tipe spesifik untuk nilai
kembaliannya. Tipe tersebut akan ditentukan oleh kompiler secara otomatis.
 Walaupun merupakan sebuah fungsi, lambda tidak membutuhkan kata
kunci fun dan visibility modifier saat dideklarasikan, karena lambda
bersifat anonymous.
 Parameter yang akan ditetapkan berada di dalam kurung kurawal {}. 
 Ketika ingin mengembalikan nilai, kata kunci return tidak diperlukan lagi karena
kompiler akan secara otomatis mengembalikan nilai dari dalam body.
 Lambda expression dapat digunakan sebagai argumen untuk sebuah parameter dan
dapat disimpan ke dalam sebuah variabel.
Dari beberapa karakteristik di atas, lambda sangat berguna karena dapat membuat
penulisan kode menjadi lebih mudah dan sederhana. Salah satu contohnya adalah kita bisa
menghindari boilerplate code dalam menggunakan anonymous class seperti berikut:

1. val comparator = object :Runnable{


2.     override fun run() {
3.         // TODO:
4.     }
5. }

Dengan lambda, kita bisa menyederhanakannya menjadi seperti di bawah ini:

1. val comparator = Runnable {


2.     // TODO:
3. }

Menggunakan Lambda Expression


Setelah mengetahui apa itu lambda dan karakteristiknya, bagaimana cara
mendeklarasikannya? Perhatikan contoh kode di bawah ini.

1. val message = { println("Hello From Lambda") }

Kode di atas adalah contoh deklarasi dari lambda, di mana fungsi lambda di atas ditandai
dengan sepasang kurung kurawal. Di dalamnya terdapat fungsi untuk mencetak teks pada
konsol. Ketika ingin menggunakannya, kita bisa memanggilnya seperti halnya kita
memanggil sebuah fungsi pada umumnya.

1. fun main() {
2.     message()
3. }
4.  
5. val message = { println("Hello From Lambda") }
6.  
7. /*
8.    output : Hello From Lambda
9. */

Jika kita ingin menambahkan sebuah parameter pada fungsi lambda, kita bisa
menuliskannya seperti berikut:

1. fun main() {
2.     printMessage("Hello From Lambda")
3. }
4.  
5. val printMessage = { message: String -> println(message) }
6.  
7. /*
8.    output : Hello From Lambda
9. */

Seperti yang disebutkan sebelumnya, parameter dari sebuah lambda berada di dalam
kurung kurawal. Untuk membedakannya dengan body, daftar parameter yang ada
dipisahkan dengan tanda ->. Kemudian, bagaimana cara mendeklarasi lambda agar dapat
mengembalikan nilai? Untuk itu kita bisa menuliskannya seperti di bawah ini: 

1. fun main() {
2.     val length = messageLength("Hello From lambda")
3.     println("Message length $length")
4. }
5.  
6. val messageLength = { message: String -> message.length }
7.  
8. /*
9.    output : Message length 17
10. */

Bisa kita perhatikan, kita tidak membutuhkan tipe kembalian dan kata kunci return untuk
mengembalikan sebuah nilai. Pada dasarnya, kompiler akan mengembalikan nilai
berdasarkan expression atau statement di baris terakhir di dalam body.
Higher-Order Function
Dalam mendeklarasi lambda, khususnya jika ingin ditetapkan agar dapat mengembalikan
nilai, terkadang kompiler tidak dapat menentukan tipenya. Alhasil, kita perlu menuliskannya
secara eksplisit. Terdapat beberapa tipe deklarasi yang dapat kita gunakan untuk
mendeklarasi lambda, antara lain: 

1. var sum: (Int) -> Int = { value -> value + value }

Tipe deklarasi pada kode di atas adalah contoh ketika kita ingin membuat lambda yang
memiliki 1 (satu) parameter dengan tipe kembalian Int. Untuk tipe deklarasi lainnya akan kita
bahas pada modul berikutnya.

Dengan ditetapkannya tipe deklarasi pada fungsi tersebut, memungkinkan kita untuk bisa
menggunakannya sebagai argumen untuk fungsi lainnya. Contohnya seperti berikut:

1. fun main() {
2.     printResult(10 ,sum)
3. }
4.  
5. fun printResult(value: Int, sum: (Int) -> Int) {
6.     val result = sum(value)
7.     println(result)
8. }
9.  
10. var sum: (Int) -> Int = { value -> value + value }
11.  
12. /*
13.    output : 20
14. */

Atau kita bisa melampirkannya secara langsung ketika fungsi printResult() di atas dipanggil
seperti berikut:

1. fun main() {
2.     printResult(10){ value ->
3.         value + value
4.     }
5. }
6.  
7. fun printResult(value: Int, sum: (Int) -> Int) {
8.     val result = sum(value)
9.     println(result)
10. }
11.  
12. /*
13.    output : 20
14. */

Konsep ini dinamakan sebagai Higher-Order Function, yaitu sebuah fungsi yang


menggunakan fungsi lainnya sebagai parameter, menjadikan tipe kembalian, ataupun
keduanya. Yang perlu diperhatikan adalah, jika argumen terakhir dari fungsi merupakan
sebuah lambda expression, maka lambda expression tersebut ditempatkan di
luar parenthesis seperti pada contoh kode di atas.
Lambda with receiver
Setelah mengetahui bagaimana cara mendeklarasikan dan menggunakan lambda,
selanjutnya kita akan mempelajari bagaimana lambda dideklarasikan dengan receiver.
Konsep ini digunakan sebagai dasar Kotlin untuk digunakan sebagai Domain Specific
Languages (DSL).

Apa itu DSL? DSL adalah sebuah bahasa komputer yang dikhususkan untuk domain
aplikasi tertentu. Ia berbeda dengan general-purpose language yang bisa diterapkan di
semua domain aplikasi. Dengan DSL, kita bisa menuliskan kode lebih ringkas dan ekspresif.
Contoh sistem yang menerapkan DSL adalah Gradle dan sistem database yang
berbasis SQL.

Pada dasarnya sebuah lambda yang mempunyai receiver mirip seperti extension functions,


yang memungkinkan kita untuk mengakses anggota objek receiver dari dalam extension.
Pada lambda, receiver ditentukan pada saat menentukan tipe deklarasi. Contohnya seperti
di bawah ini:

1. fun buildString(action: StringBuilder.() -> Unit): String {


2.     val stringBuilder = StringBuilder()
3.     stringBuilder.action()
4.     return stringBuilder.toString()
5. }

Pada kode di atas, StringBuilder dijadikan sebagai receiver untuk tipe deklarasi


parameter action.  Dengan begitu kita dapat memanggil parameter action tersebut dari
variabel yang bertipekan StringBuilder. Untuk memanggil fungsi di atas bisa seperti berikut:

1. fun main() {
2.     val message = buildString {
3.         append("Hello ")
4.         append("from ")
5.         append("lambda ")
6.     }
7.  
8.     println(message)
9. }
10.  
11. fun buildString(action: StringBuilder.() -> Unit): String {
12.     val stringBuilder = StringBuilder()
13.     stringBuilder.action()
14.     return stringBuilder.toString()
15. }
16.  
17. /*
18.    output : Hello from lambda
19. */

Pada dasarnya parameter action di atas dipanggil ketika kita melampirkan argumen lambda
saat fungsi tersebut digunakan. Kemudian bisa kita perhatikan, parenthesis tidak diperlukan
karena fungsi tersebut hanya memiliki satu parameter yaitu sebuah lambda expression. 
Kotlin Standard Library 
Kotlin hadir dengan berbagai fitur menarik yang sudah kita bahas pada modul - modul
sebelumnya. Salah satu fitur yang selanjutnya perlu kita ketahui adalah standard function
library, yaitu sebuah extension functions dari sebuah objek yang menggunakan lambda
sebagai argumen atau yang disebut sebagai higher-order function.

Scope Function
Kotlin standard library memiliki beberapa fungsi yang tujuan utamanya adalah bagaimana
menuliskan logika kode di dalam konteks dari sebuah objek. Dalam menggunakan fungsi
tersebut kita akan memanfaatkan lambda expression yang memiliki ruang lingkupnya
sendiri, di mana dalam ruang lingkup tersebut kita dapat mengakses konteks dari sebuah
objek.

Fungsi inilah yang dinamakan sebagai scope function. Beberapa fungsi yang berada di
dalamnya antara lain let, run, with, apply, dan also. Pada dasarnya beberapa fungsi
tersebut melakukan tugas yang sama yaitu mengeksekusi blok kode dari dalam sebuah
objek. Yang membedakannya adalah bagaimana objek tersebut tersedia dan seperti apa
hasil yang didapat dari seluruh expression yang berada di dalam blok.

Context Object
Sebelum kita mengulas beberapa fungsi yang menjadi bagian dari scope function di atas,
kita perlu mengetahui terlebih dahulu bagaimana cara mengakses konteks sebuah objek
dari dalam lambda yang menjadi bagian dari scope function. 

Terdapat 2 (dua) cara, yaitu: diakses sebagai lambda receiver (this) dan lambda argumen
(it). Keduanya memiliki kapabilitas yang sama dan tentunya digunakan untuk kasus yang
berbeda.

Lambda receiver (this)


Beberapa fungsi yang menggunakan lambda receiver adalah run, with, dan apply. Ketika
ingin mengakses konteks dari sebuah objek, kita bisa saja tidak menuliskan atau
menghilangkan kata kunci this. Misalnya seperti penggunaan fungsi apply berikut:

1. val buildString = StringBuilder().apply {


2.     append("Hello ")
3.     append("Kotlin ")
4. }

Cara ini memiliki kekurangan yaitu kita tidak dapat membedakan objek receiver dengan
objek yang berada dari luar lingkup fungsi tersebut. Alih-alih, cara ini lebih ditujukan untuk
operasi objek itu sendiri, seperti memanggil fungsi dan inisialisasi nilai dari anggota yang
menjadi bagian dari objek tersebut.

Lambda argument (it)


Selanjutnya, fungsi yang menggunakan lambda argument untuk mengakses konteks dari
sebuah objek adalah fungsi let dan also. Berbeda dengan lambda receiver, nilai dari
argumen tersebut dapat kita gunakan untuk diproduksi atau di inisialisasikan untuk variabel
lain. Contohnya seperti berikut:

1. val text = "Hello"


2. text.let {
3.     val message = "$it Kotlin"
4.     println(message)
5. }

Secara default, nama dari argumen tersebut adalah it, namun kita dapat mengubahnya
seperti berikut:

1. val text = "Hello"


2. text.let { value ->
3.     val message = "$value Kotlin"
4.     println(message)
5. }
run
Fungsi run akan mengembalikan nilai berdasarkan expression yang berada di dalam blok
lambda. Untuk mengakses konteks dari objek, ia akan menggunakan receiver (this).
Fungsi run akan sangat berguna jika di dalam blok lambda terdapat inisialisasi objek dan
perhitungan untuk nilai kembalian. Contoh penggunaannya seperti berikut:

1. fun main() {
2.     val text = "Hello"
3.     val result = text.run {
4.         val from = this
5.         val to = this.replace("Hello", "Kotlin")
6.         "replace text from $from to $to"
7.     }
8.     println("result : $result")
9. }
10.  
11. /*
12.    output : result : replace text from Hello to Kotlin
13. */

with
Selanjutnya fungsi with. Pada dasarnya fungsi with bukanlah sebuah extension melainkan
hanyalah fungsi biasa. Konteks objeknya disematkan sebagai argumen dan dari blok
lambda diakses sebagai receiver. Contohnya seperti berikut:

1. fun main() {
2.     val message = "Hello Kotlin!"
3.     val result = with(message) {
4.         println("value is $this")
5.         println("with length ${this.length}")
6.     }
7. }

Nilai yang akan dikembalikan adalah berdasarkan expression yang berada di dalam blok


lambda. Misalnya seperti berikut:

1. fun main() {
2.     val message = "Hello Kotlin!"
3.     val result = with(message) {
4.         "First character is ${this[0]}" +
5.                 " and last character is ${this[this.length - 1]}"
6.     }
7.  
8.     println(result)
9. }
10.  
11. /*
12.    output : First character is H and last character is !
13. */

Fungsi with sendiri disarankan untuk mengakses apa yang menjadi anggotanya tanpa harus
menyediakan nilai kembalian.

apply
Berbeda dengan fungsi-fungsi sebelumnya, nilai yang dikembalikan dari fungsi apply adalah
nilai dari konteks objeknya dan objek konteksnya tersedia sebagai receiver (this). Baiknya
fungsi apply dapat melakukan inisialisasi atau konfigurasi dari receiver-nya. Perhatikan kode
berikut:

1. fun main() {
2.     val builder = StringBuilder()
3.     builder.append("Hello ")
4.     builder.append("Kotlin!")
5.  
6.     println(builder.toString())
7. }
8.  
9. /*
10.    output : Hello Kotlin
11. */

Dengan fungsi apply kita bisa menuliskannya seperti di bawah ini:

1. fun main() {
2.     val message = StringBuilder().apply {
3.         append("Hello ")
4.         append("Kotlin!")
5.     }
6.  
7.     println(message.toString())
8. }
9.  
10. /*
11.    output : Hello Kotlin
12. */
let
Fungsi let menggunakan argumen (it) untuk mengakses konteks dari sebuah objek.
Penggunaan fungsi let akan banyak kita temukan pada objek yang bertipe non-null.
Contohnya seperti di bawah ini:

1. fun main() {
2.     val message: String? = null
3.     message?.let {
4.         val length = it.length * 2
5.         val text = "text length $length"
6.         println(text)
7.     }
8. }

Dengan menggunakan fungsi let seperti pada kode di atas, kita bisa mengurangi


penggunaan operator safe call seperti berikut:

1. fun main() {
2.     val message: String? = null
3.     val length = message?.length ?: 0 * 2
4.     val text = "text length $length"
5.     println(text)
6. }

Lalu bagaimana jika kita ingin menjalankan logika kode lain jika objeknya bernilai null? Di
sini kita akan memanfaatkan fungsi lainnya yaitu run dan elvis operator yang sudah kita
pelajari sebelumnya. Contohnya seperti berikut:

1. fun main() {
2.     val message: String? = null
3.     message?.let {
4.         val length = it.length * 2
5.         val text = "text length $length"
6.         println(text)
7.     } ?: run {
8.         val text = "message is null"
9.         println(text)
10.     }
11. }

Sedangkan untuk nilai kembalian, ia bergantung pada expression yang berada di dalam blok


lambda seperti pada contoh di atas. Karena pada baris terakhir dari blok lambda tersebut
adalah fungsi println(), maka nilai yang akan dikembalikan adalah Unit. Ini dikarenakan
fungsi println() sendiri mengembalikan nilai Unit.

also
Fungsi also sama seperti fungsi apply, di mana nilai yang dikembalikan adalah nilai dari
konteks objek. Namun untuk konteks objeknya tersedia sebagai argumen (it). Fungsi also
baiknya digunakan ketika kita ingin menggunakan konteks dari objek sebagai argumen
tanpa harus mengubah nilainya. 

1. fun main() {
2.     val text = "Hello Kotlin"
3.     val result = text.also {
4.         println("value length -> ${it.length}")
5.     }
6.  
7.     println("text -> $result")
8. }
9.  
10. /*
11.    output :
12.            value length -> 12
13.            text -> Hello Kotlin
14. */

Nah untuk melihat bagaimana beberapa fungsi yang sudah kita bahas di atas , cek tautan
ini.
Seperti yang sudah kita pelajari pada modul sebelumnya, saat mendeklarasikan sebuah
lambda dengan function type, kita bisa menggunakannya seperti berikut:

1. val sum: (Int, Int) -> Int = { valueA, valueB -> valueA + valueB }

Dengan Kotlin, kita bisa memisahkan lambda expression menjadi fungsi tersendiri dan
mereferensikannya langsung sebagai instance dari function type dengan cara seperti di
bawah ini:

1. val sum: (Int, Int) -> Int = ::count


2. fun count(valueA: Int, valueB
3. : Int): Int {
4.    return valueA + valueB
5. }

Kode di atas ditulis dengan mekanisme Reflection yang berarti seperangkat fitur bahasa


dan library yang memungkinkan kita untuk mengamati struktur kode dari proyek yang
sedang kita kerjakan secara langsung.

Function References
Pada suatu kondisi, terkadang kita butuh mereferensikan sebuah fungsi. Sebagai contoh,
misal kita memiliki fungsi seperti berikut:

1. fun isEvenNumber(number: Int) = number % 2 == 0

Fungsi di atas digunakan untuk memeriksa apakah suatu angka merupakan sebuah
bilangan genap. Dengan menggunakan operator :: kita bisa menggunakannya
sebagai instances dari function type. Sebagai contoh, penggunaan fungsi filter() yang
menjadi bagian dari kelas List berikut:

1. fun main() {
2.    val numbers = 1.rangeTo(10)
3.    val evenNumbers = numbers.filter(::isEvenNumber)
4.  
5.    println(evenNumbers)
6. }
7.  
8. fun isEvenNumber(number: Int) = number % 2 == 0
9.  
10. /*
11.    output = [2, 4, 6, 8, 10]
12. */

Selain itu, kita juga bisa mereferensikan sebuah extensions function. Caranya dengan ikut
menyertakan objek yang menjadi receivernya sebelum operator :: seperti berikut:

1. fun main() {
2.    val numbers = 1.rangeTo(10)
3.    val evenNumbers = numbers.filter(Int::isEvenNumber)
4.  
5.    println(evenNumbers)
6. }
7.  
8. fun Int.isEvenNumber() = this % 2 == 0
9.  
10. /*
11.    output = [2, 4, 6, 8, 10]
12. */
Property References
Selain digunakan untuk mereferensikan sebuah fungsi. Operator :: juga dapat digunakan
untuk mereferensikan sebuah properti. Dengan Operator, kita bisa mengakses apa yang
menjadi bagian dari properti tersebut seperti nama, fungsi setter getter, dll. Contohnya
seperti berikut:

1. var message = "Kotlin"


2.  
3. fun main() {
4.    println(::message.name)
5.    println(::message.get())
6.  
7.    ::message.set("Kotlin Academy")
8.  
9.    println(::message.get())
10. }

Ekspresi ::message akan dievaluasi ke dalam objek dengan KMutableProperty yang


memungkinkan kita untuk membaca nilainya dengan menggunakan get(), menetapkan nilai
menggunakan set() dan mendapatkan nama dari properti tersebut menggunakan
properti name. [7]

Sedangkan untuk properti yang bersifat immutable seperti val message =


“Kotlin”, ::message akan mengembalikan nilai dengan tipe KProperty, yang mana hanya
terdapat fungsi get() di dalamnya. [8]

1. val message = "Kotlin"


2.  
3. fun main() {
4.    println(::message.name)
5.    println(::message.get())
6.  
7.    // ::message.set("Kotlin Academy") <- Error : Unresolved reference.
8.  
9.    println(::message.get())
10. }

Untuk mengetahui lebih tentang KMutableProperty, Anda bisa


membaca disini dan KProperty di sini.
Ketika mengembangkan sebuah proyek, kita pasti membuat beberapa fungsi tersendiri
dengan tujuan untuk memisahkan logika program dari fungsi utama. Tujuannya adalah agar
kode lebih terstruktur dan mudah dibaca. Namun pada praktiknya, terkadang kode yang ada
pada fungsi tersebut malah lebih panjang dan susah dibaca. Salah satu penyebabnya
adalah karena penulisan kode yang berulang atau lainnya.

Untuk mengatasinya, kita bisa memisahkannya lagi menjadi sebuah fungsi lokal (inner
function) dengan hak akses terbatas hanya untuk fungsi terluarnya. Ini bisa dilakukan
karena pada Kotlin kita bisa mendefinisikan sebuah fungsi di mana pun, bahkan di dalam
sebuah fungsi (function inside function).

Berikut adalah contoh dari sebuah inner function:

1. fun setWord(message: String) {


2.    fun printMessage(text: String) {
3.        println(text)
4.    }
5.  
6.    printMessage(message)
7. }

Bisa diperhatikan bahwa fungsi printMessage() didefinisikan di dalam fungsi setWord().


Mendefinisikan sebuah inner function sama halnya seperti kita mendefinisikan sebuah fungsi
seperti biasanya. Menariknya, kita bisa mengakses apa yang menjadi bagian fungsi
terluarnya. Contoh, parameter dari fungsi setWord() bisa diakses dari dalam fungsi print
sehingga kode di atas bisa diubah menjadi seperti berikut:

1. fun setWord(message: String) {


2.    fun printMessage() {
3.        println(message)
4.    }
5.  
6.    printMessage()
7. }

Lebih sederhana bukan? Perlu diperhatikan, inner function hanya bisa diakses setelah


fungsi tersebut didefinisikan. Jika kita coba mengaksesnya, maka akan tampil eror seperti
berikut:

1. fun setWord(message: String) {


2.    printMessage() // ERROR: Unresolved references
3.  
4.    fun printMessage() {
5.        println(message)
6.    }
7.  
8.    printMessage()
9. }

Lalu, pada kondisi seperti apa kita bisa memanfaatkan inner function? Perhatikan deklarasi
fungsi berikut di bawah ini:

1. fun sum(valueA: Int, valueB: Int, valueC: Int): Int {


2.    if (valueA == 0) {
3.        throw IllegalArgumentException("valueA must be better than 0")
4.    }
5.  
6.    if (valueB == 0) {
7.        throw IllegalArgumentException("valueB must be better than 0")
8.    }
9.  
10.    if (valueC == 0) {
11.        throw IllegalArgumentException("valueC must be better than 0")
12.    }
13.  
14.    return valueA + valueB + valueC
15. }

Tidak ada yang salah dengan semua fungsi di atas. Fungsi tersebut akan berjalan dengan
semestinya tanpa adanya eror selama kondisi yang berada di dalamnya tidak terpenuhi.
Namun jika kita perhatikan, terdapat pengulangan kode yang sama yaitu penggunaan if
expression untuk memeriksa apakah nilai dari argumen yang diberikan bernilai null.

Di sinilah kita bisa memanfaatkan inner function untuk membuat kode yang ditulis berulang
tersebut menjadi fungsi tersendiri.

1. fun sum(valueA: Int, valueB: Int, valueC: Int): Int {


2.    fun validateNumber(value: Int) {
3.        if (value == 0) {
4.            throw IllegalArgumentException("value must be better than 0")
5.        }
6.    }
7.  
8.    validateNumber(valueA)
9.    validateNumber(valueB)
10.    validateNumber(valueC)
11.  
12.    return valueA + valueB + valueC
13. }

Setelah menjadikannya sebagai sebuah fungsi tersendiri, kode yang ada di dalam
fungsi sum() tersebut lebih singkat dan tentunya lebih mudah dibaca dibandingkan
sebelumnya.

Selain itu, kita juga bisa menjadikan inner function sebagai extensions function. Contohnya


seperti berikut:

1. fun sum(valueA: Int, valueB: Int, valueC: Int): Int {


2.    fun Int.validateNumber(){
3.        if (this == 0) {
4.            throw IllegalArgumentException("value must be better than 0")
5.        }
6.    }
7.  
8.    valueA.validateNumber()
9.    valueB.validateNumber()
10.    valueC.validateNumber()
11.  
12.    return valueA + valueB + valueC
13. }
Kotlin Collection adalah salah satu struktur data mumpuni yang banyak menyediakan fungsi
untuk memudahkan kita dalam mengelola dan memanipulasi data. Pada modul-modul
sebelumnya, kita sudah mempelajari beberapa fungsi yang disediakan
seperti map(), sum(), sorted(), dan sebagainya.

Pada modul ini kita akan mempelajari beberapa fungsi tingkat lanjut lainnya yang tentunya
bisa kita manfaatkan untuk mengelola data seperti yang disebutkan di atas.

Fold
Langsung saja kita mulai dengan fungsi fold, kita bisa dengan mudah melakukan
perhitungan setiap nilai yang berada di dalam sebuah collection tanpa harus melakukan
iterasi item tersebut satu-persatu menggunakan fungsi fold(). Untuk contoh penggunaannya
adalah sebagai berikut:

1. val numbers = listOf(1, 2, 3)


2. val fold = numbers.fold(10) { current, item ->
3.    println("current $current")
4.    println("item $item")
5.    println()
6.    current + item
7. }
8.  
9. println("Fold result: $fold")
10.  
11. /*output:
12.        current 10
13.        item 1
14.  
15.        current 11
16.        item 2
17.  
18.        current 13
19.        item 3
20.  
21.        Fold result: 16
22. */

Fungsi fold() memerlukan 2 (dua) argumen yaitu nilai awal untuk perhitungan dan lambda
expression yang nilai kembaliannya digunakan untuk menentukan nilai awal selanjutnya.
Nah, di dalam lambda expression nya juga terdapat 2 (dua) argumen. Yaitu,
argumen current yang merepresentasikan nilai awal dan argumen item merepresentasikan
masing-masing item yang berada pada numbers.

Selain itu, terdapat juga fungsi fold lainnya yaitu foldRight(). Berbeda dengan fungsi fold(),


fungsi foldRight() akan melakukan proses iterasi dari indeks terakhir dan posisi dari argumen
pada lambda expression nya pun berbeda, di mana argumen item berada pada posisi
pertama dan argumen current berada pada posisi kedua. Contohnya seperti berikut:

1. val numbers = listOf(1, 2, 3)


2. val fold = numbers.foldRight(10) { item, current ->
3.    println("current $current")
4.    println("item $item")
5.    println()
6.    item + current
7. }
8.  
9. println("Fold result: $fold")
10.  
11. /*output:
12.        current 10
13.        item 3
14.  
15.        current 13
16.        item 2
17.  
18.        current 15
19.        item 1
20.  
21.        Fold result: 16
22. */

Drop
Selanjutnya adalah fungsi drop(), fungsi yang bisa kita manfaatkan untuk memangkas item
yang berada di dalam sebuah objek collection berdasarkan jumlah yang kita tentukan.
Sebagai contoh, saat kita mempunyai sebuah collection seperti berikut:

1. val number = listOf(1, 2, 3, 4, 5, 6)

Kemudian kita ingin memangkas 3 (tiga) item dari collection di atas. Dengan fungsi drop(),
kita bisa melakukannya seperti di bawah ini:

1. val number = listOf(1, 2, 3, 4, 5, 6)


2. val drop = number.drop(3)
3.  
4. println(drop)
5. /*
6.    output: [4, 5, 6]
7. */

Seperti yang dijelaskan sebelumnya, nilai 3 yang menjadi argumen dari fungsi drop() di atas
adalah jumlah item yang akan dipangkas. Pemangkasan dimulai dari posisi atau indeks
pertama, lalu bagaimana jika kita ingin memangkas nilai dari indeks terakhir? Kita bisa
menggunakan fungsi dropLast(). Contohnya seperti berikut:

1. val number = listOf(1, 2, 3, 4, 5, 6)


2. val drop = number.dropLast(3)
3.  
4. println(drop)
5.  
6. /*
7.    output: [1, 2, 3]
8. */

Take
Jika fungsi drop() digunakan untuk memangkas, fungsi take() bisa kita manfaatkan untuk
menyaring item yang berada di dalam sebuah objek collection. Pemanggilan
fungsi take() sama halnya seperti fungsi drop() di mana kita perlu menentukan jumlah item
yang akan disaring. Berikut contoh penggunaannya:

1. val total = listOf(1, 2, 3, 4, 5, 6)


2. val take = total.take(3)
3.  
4. println(take)
5.  
6. /*
7.    output: [1, 2, 3]
8. */
Kotlin juga menyediakan fungsi seperti dropLast() yang menjalankan operasi dari posisi atau
indeks terakhir yaitu takeLast(). Contohnya seperti berikut:

1. val total = listOf(1, 2, 3, 4, 5, 6)


2. val take = total.takeLast(3)
3.  
4. println(take)
5.  
6. /*
7.    output: [4, 5, 6]
8. */
Slice
Setelah pembahasan fungsi take() pada modul sebelumnya, muncul pertanyaan, bagaimana
jika kita ingin menyaring item dari posisi tertentu? Untuk itu kita bisa memanfaatkan
fungsi slice(). Dalam penggunaannya, fungsi slice() membutuhkan sebuah argumen berupa
Range yang digunakan untuk menentukan posisi pertama dan terakhir yang akan disaring.
Berikut contohnya:

1. val total = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val slice = total.slice(3..6)
3.  
4. println(slice)
5.  
6. /*
7.    output: [4, 5, 6, 7]
8. */

Karena menggunakan Range, kita juga bisa menggunakan operator step ketika


argumennya disematkan seperti berikut:

1. val total = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)


2. val slice = total.slice(3..6 step 2)
3.  
4. println(slice)
5.  
6. /*
7.    output: [4, 6]
8. */

Kemudian jika ingin menentukan posisi yang lebih spesifik, kita bisa mendefinisikannya di
dalam sebuah collection, kemudian disematkan sebagai argumen. Misal seperti di bawah
berikut:

1. val index = listOf(2, 3, 5, 8)


2. val total = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
3. val slice = total.slice(index)
4.  
5. println(slice)
6.  
7. /*
8.    output: [3, 4, 6, 9]
9. */

Kita harus berhati-hati dalam menentukan cakupan index untuk mendapatkan data. Karena
dapat menyebabkan terjadinya ArrayIndexOutOfBoundsException jika posisi yang
ditentukan tidak terdapat pada objek collection.

Distinct
Saat berurusan dengan item yang sama di dalam sebuah collection, untuk menyaring item
yang sama tersebut kita akan melakukan iterasi dan membandingkan setiap itemnya.
Namun dengan Kotlin kita tidak perlu melakukannya secara manual, karena Kotlin Collection
menyediakan fungsi untuk melakukannya dengan mudah yaitu fungsi distinct(). Sebagai
contoh:

1. val total = listOf(1, 2, 1, 3, 4, 5, 2, 3, 4, 5)


2. val distinct = total.distinct()
3.  
4. println(distinct)
5.  
6. /*
7.    output: [1, 2, 3, 4, 5]
8. */

Sama halnya seperti beberapa fungsi sebelumnya yang sudah dibahas, fungsi distinct() bisa
langsung dipanggil dari objek collection. Kita juga bisa menggunakannya
pada collection dengan tipe parameter seperti data class. Misal seperti berikut:

1. data class Item(val key: String, val value: Any)


2.  
3. val items = listOf(
4.    Item("1", "Kotlin"),
5.    Item("2", "is"),
6.    Item("3", "Awesome"),
7.    Item("3", "as"),
8.    Item("3", "Programming"),
9.    Item("3", "Language")
10. )
11.  
12. val distinctItems = items.distinctBy { it.key }
13. distinctItems.forEach {
14.    println("${it.key} with value ${it.value}")
15. }
16.  
17. /*
18.    output:
19.        1 with value Kotlin
20.        2 with value is
21.        3 with value Awesome
22. */

Menariknya, kita bisa juga menentukan proses penyaringan item yang sama seperti apa
yang akan dijalankan dengan menggunakan fungsi distinctBy(). Misal kita ingin menyaring
item yang memiliki panjang sama, kita bisa melakukannya seperti berikut:

1. val text = listOf("A", "B", "CC", "DD", "EEE", "F", "GGGG")


2. val distinct = text.distinctBy {
3.    it.length
4. }
5.  
6. println(distinct)
7.  
8. /*
9.    output: [A, CC, EEE, GGGG]
10. */

Yang perlu diperhatikan, fungsi distinct() tidak dapat digunakan pada object Map Collection.

Chunked
Sama seperti fungsi split(), fungsi chunked() bisa kita gunakan untuk memecah nilai String
menjadi beberapa bagian kecil dalam bentuk Array. Namun untuk penerapannya sedikit
berbeda, di mana fungsi split() membutuhkan argumen berupa RegEx sebagai parameter
sedangkan chunked() membutuhkan nilai yang akan digunakan sebagai jumlah indeks untuk
pemisah. Contoh penggunaannya seperti berikut:

1. val word = "QWERTY"


2. val chunked = word.chunked(3)
3.  
4. println(chunked)
5.  
6. /*
7.    output:[QWE, RTY]
8. */

Selain itu, kita juga bisa menggunakannya untuk memodifikasi setiap nilai yang sudah
dipecah. Contoh, hasil dari nilai yang sudah dipecah ingin kita buat menjadi huruf kecil,
maka kita bisa menggunakan fungsi chunked() seperti berikut: 

1. val word = "QWERTY"


2. val chunkedTransform = word.chunked(3) {
3.    it.toString().toLowerCase()
4. }
5.  
6. println(chunkedTransform)
7.  
8. /*
9.    output: [qwe, rty]
10. */

Argumen yang berada pada lambda expression di atas merepresentasikan setiap nilai yang
sudah dipecah.
Recursion merupakan sebuah teknik dasar dalam pemrograman yang bisa kita gunakan
untuk menyederhanakan pemecahan masalah yang umumnya diselesaikan dengan cara
yang kompleks. Di Kotlin, recursion disebut juga dengan recursive function.

Recursive function adalah sebuah mekanisme di mana sebuah fungsi digunakan dari dalam
fungsi itu sendiri. Ini memungkinkan sebuah fungsi dapat berjalan beberapa kali. Setiap
pemanggilannya bisa kita atur agar dapat mengembalikan nilai dan digunakan sebagai
argumen untuk pemanggilan fungsi berikutnya serta mengembalikan nilai akhir berupa
perhitungan nilai kembalian dari setiap pemanggilan fungsi tersebut.

Lalu penyelesaian seperti apa yang dapat kita lakukan dengan recursive? Perhatikan kode
di bawah ini:

1. fun factorial(n: Int): Int {


2.    return if (n == 1) {
3.        n
4.    } else {
5.        var result = 1
6.        for (i in 1..n) {
7.            result *= i
8.        }
9.        result
10.    }
11. }

Fungsi di atas adalah contoh bagaimana menghitung faktorial dari nilai yang kita tentukan.
Nah, tidak ada yang salah dengan kode tersebut dan dapat dijalankan serta mengembalikan
nilai sesuai dengan yang kita inginkan. Namun jika kita perhatikan, untuk menghitung nilai
akhir, kode di atas menggunakan for loop yang di setiap iterasinya terdapat proses
perhitungan nilai yang akan dikembalikan sebagai nilai akhir. Dengan recursive kita bisa
menentukan nilai akhir tersebut dengan cara yang lebih sederhana. Berikut contoh ketika
kode di atas ditulis dengan mekanisme recursive:

1. fun factorial(n: Int): Int {


2.    return if (n == 1) {
3.        n
4.    } else {
5.        n * factorial(n - 1)
6.    }
7. }

Ketika kita menjalankan fungsi di atas, program akan menciptakan tumpukan frame dengan


jumlah berdasarkan nilai n di mana setiap frame akan mengkonsumsi memori. Ini bisa jadi
masalah dalam penerapannya. Contoh, jika kita memasukkan argumen dengan nilai besar
ketika ingin menggunakannya seperti berikut:

1. fun main() {
2.    println("Factorial 9999 is: ${factorial(9999)}")
3. }
4.  
5.  
6. fun factorial(n: Int): Int {
7.    return if (n == 1) {
8.        n
9.    } else {
10.        n * factorial(n - 1)
11.    }
12. }

Maka pada konsol akan menampilkan eror berikut:


Exception in thread "main" java.lang.StackOverflowError

Tail Call Recursion


Namun kita tidak perlu khawatir dengan masalah seperti di atas. Kotlin mendukung gaya
pemrograman fungsional yang bernama tail recursion yakni sekumpulan urutan instruksi
untuk menjalankan tugas tertentu (subroutine) yang dijalankan terakhir pada sebuah
prosedur.

Dengannya, kita bisa meminimalisir penumpukan frame ketika kita menerapkan


recursive. Tail recursion akan memastikan proses sebelumnya telah selesai sebelum
pemanggilan fungsi berikutnya dijalankan. Contohnya adalah seperti berikut:

1. fun factorial(n: Int, result: Int = 1): Int {


2.     val newResult = n * result
3.     return if (n == 1) {
4.         newResult
5.     } else {
6.         factorial(n - 1, newResult)
7.     }
8. }

Namun dengan kode di atas kita tidak bisa langsung menghindari penumpukan frame. Ini
karena secara default JVM tidak mendukung optimasi tail recursion. Untuk itu, Kotlin
menyediakan modifier agar kita bisa tetap menerapkannya, yaitu modifier tailrec.
Penggunaannya adalah seperti berikut:

1. tailrec fun factorial(n: Int, result: Int = 1): Int {


2.     val newResult = n * result
3.     return if (n == 1) {
4.         newResult
5.     } else {
6.         factorial(n - 1, newResult)
7.     }
8. }

Pada kode di atas, modifier tailrec ditempatkan sebelum kata kunci fun. Ketika sebuah


fungsi ditandai dengan modifier tailrec, maka fungsi tersebut hanya boleh dipanggil untuk
dijalankan terakhir dan tidak boleh digunakan dari dalam blok try-catch-finally.
Rangkuman dari Kotlin Functional Programming
Sebelum lanjut ke paradigma Object-Oriented Programming, mari kita rangkum apa saja
yang sudah Anda pelajari pada modul Functional Programming.

 Anatomi dari sebuah function terdiri dari 2 (adua) bagian utama, yaitu function
header dan function body:

o Function Header
Function header adalah bagian yang merupakan konstruksi dari sebuah
fungsi untuk menentukan perilakunya akan seperti apa. Di dalam function
header terdapat visibility modifier, kata kunci fun, nama, daftar parameter dan
nilai kembalian dari fungsi tersebut.
o Function Body
Function Body adalah bagian yang dalamnya kita akan menempatkan sebuah
logika kode baik itu sebuah expression atau statement.
 Kotlin memiliki fitur seperti named dan default argument yang dapat menghindari kita
dari kesalahan saat menyematkan sebuah argumen saat menggunakan sebuah
fungsi.
 Selain named dan default argument, Kotlin juga memiliki fitur varargs yang dapat
digunakan untuk menyederhanakan deklarasi beberapa parameter yang memiliki tipe
yang sama.
 Kotlin mendukung 2 (dua) extension yang dapat digunakan, yaitu Extension
Functions dan Extension Properties. Jika extension functions digunakan untuk
menambahkan fungsi baru, extension properties tentunya digunakan untuk
menambahkan sebuah properti baru.
 Lambda expression, atau biasa disebut dengan anonymous function atau function
literal adalah fitur yang cukup populer sampai sekarang dalam dunia functional
programming. Bisa disebut sebagai anonymous karena lambda tidak memiliki
sebuah nama seperti halnya sebuah fungsi pada umumnya. Karena merupakan
sebuah fungsi, lambda juga dapat memiliki daftar parameter, body dan return type.
Berikut adalah beberapa karakteristik dari Lambda pada kotlin.

o Dalam menggunakan lambda, kita tidak perlu mendeklarasi tipe spesifik


untuk nilai kembaliannya. Tipe tersebut akan ditentukan oleh kompiler secara
otomatis.
o Walaupun merupakan sebuah fungsi, lambda tidak membutuhkan kata kunci
fun dan visibility modifier saat dideklarasikan, karena lambda bersifat
anonymous.
o Parameter yang akan ditetapkan berada di dalam kurung kurawal {}. 
o Ketika ingin mengembalikan nilai, kata kunci return tidak diperlukan lagi
karena kompiler akan secara otomatis mengembalikan nilai dari dalam body.
o Lambda expression dapat digunakan sebagai argumen untuk sebuah
parameter dan dapat disimpan ke dalam sebuah variabel.
 Higher-Order Function, yaitu sebuah fungsi yang menggunakan fungsi lainnya
sebagai parameter, menjadikan tipe kembalian, ataupun keduanya. High-order
function adalah salah satu fitur yang memanfaatkan Lambda dan .
 Kotlin standard library memiliki beberapa fungsi yang tujuan utamanya adalah
bagaimana menuliskan logika kode di dalam konteks dari sebuah objek. Dalam
menggunakan fungsi tersebut kita akan memanfaatkan lambda expression yang
memiliki ruang lingkupnya sendiri, di mana dalam ruang lingkup tersebut kita dapat
mengakses konteks dari sebuah objek. Fungsi inilah yang dinamakan sebagai scope
function. Beberapa fungsi yang berada di dalamnya antara lain, let, run, with, apply,
dan also.
 Sama seperti Java, Kotlin  mendukung juga mekanisme reflection yang berarti
seperangkat fitur bahasa dan library yang memungkinkan kita untuk mengamati
struktur kode dari proyek yang sedang kita kerjakan secara langsung.
 Saat menggunakan Kotlin, kita bisa mendeklarasi fungsi di dalam sebuah fungsi dan
menggunakannya hanya di  dalam fungsi tersebut.
 Kotlin Collection adalah salah satu struktur data mumpuni yang banyak menyediakan
fungsi untuk memudahkan kita dalam mengelola dan memanipulasi data. Pada
modul-modul sebelumnya, kita sudah mempelajari beberapa fungsi yang disediakan
seperti map(), sum(), dan sorted(). Selain beberapa fungsi yang sudah disebutkan,
berikut fungsi lain yang dapat kita manfaatkan:

o Fold
Fold, Anda bisa dengan mudah melakukan perhitungan setiap nilai yang
berada di dalam sebuah collection tanpa harus melakukan iterasi item
tersebut satu-persatu menggunakan fungsi fold().
o Drop
drop(), fungsi yang bisa kita manfaatkan untuk memangkas item yang berada
di dalam sebuah objek collection berdasarkan jumlah yang kita tentukan.
o Take
Fungsi take() bisa kita manfaatkan untuk menyaring item yang berada di
dalam sebuah objek collection.
o Slice
Bagaimana jika kita ingin menyaring item dari posisi tertentu? Untuk itu Anda
bisa memanfaatkan fungsi slice().
o Distinct
Saat berurusan dengan item yang sama di dalam sebuah collection, untuk
menyaring item yang sama tersebut kita akan melakukan iterasi dan
membandingkan setiap itemnya. Namun dengan Kotlin kita tidak perlu
melakukannya secara manual, karena Kotlin Collection menyediakan fungsi
untuk melakukannya dengan mudah yaitu fungsi distinct().
o Chunked
Sama seperti fungsi split(), fungsi chunked() bisa kita gunakan untuk
memecah nilai String menjadi beberapa bagian kecil dalam bentuk Array.
 Recursion merupakan sebuah teknik dasar dalam pemrograman yang bisa kita
gunakan untuk menyederhanakan pemecahan masalah yang umumnya diselesaikan
dengan cara yang kompleks. Di Kotlin, recursion disebut juga dengan recursive
function.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Ikuti tutorial berikut untuk menginstal plugin EduTools pada IntelliJ IDEA:

 Menginstal EduTools pada IntelliJ IDEA.

Setelah EduTools berhasil diinstal, buka proyek latihan dengan mengikuti tutorial berikut:

 Membuka dan Mengerjakan latihan akademi Memulai Pemrograman dengan Kotlin.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari seperti apa functional programming pada Kotlin dan fitur - fitur apa
saja pada Kotlin yang terkait dengan functional programming.

Untuk menguji pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu
mengerjakan beberapa latihan sederhana. Buka proyek latihan dan kerjakan semua latihan
yang ada pada modul Functional Programming.
Object-Oriented Programming
Pada modul awal kita sudah mengetahui bahwa Kotlin memberi dukungan luas untuk
mengembangkan program berorientasi objek. Sebabnya, OOP masih menjadi salah satu
paradigma atau teknik pemrograman yang banyak digunakan dalam pengembangan
aplikasi. Dengan paradigma OOP kita dapat mudah memvisualisasikan kode karena OOP
sendiri mirip seperti skenario dalam kehidupan nyata. 

Visualisasi di atas mencontohkan gambaran umum OOP di mana terdapat


sebuah blueprint mobil, komponen yang dimiliki mobil, dan kemampuan yang dapat
dilakukan oleh mobil. Dalam OOP blueprint tersebut dikenal dengan Class (kelas),
komponen dikenal dengan nama atribut, kemampuan yang dimiliki dikenal sebagai
behaviour dan hasil realisasi dari sebuah blueprint tersebut disebut object.

Pada modul ini kita akan membahas secara detail


tentang object, classes, attribute dan behaviour yang ada pada OOP. 
Object Everywhere
Pada modul Data Types telah disebutkan bahwa pada Kotlin semua bertindak sebagai
objek di mana kita bisa memanggil member function dan properti dari sebuah variabel.
Objek merupakan hasil realisasi dari sebuah blueprint atau class yang tentunya memiliki
fungsi dan juga properti sama seperti blueprint-nya. Artinya, dengan membuat objek kita
dapat mengakses fungsi dan properti yang terdapat pada kelas tersebut.

Pada Kotlin, nilai primitif seperti String, Integer, Char, Boolean merupakan


sebuah Object. Hal ini berbeda dengan bahasa pemrograman lain. Maka dari itu, terdapat
sebuah istilah yang terkenal di  Kotlin, yaitu “Object Everywhere”. Perhatikan kode berikut:

1. val someString = “Dicoding”

Pada kode tersebut kita melakukan pembuatan variabel yang juga merupakan sebuah objek
dengan nama someString. Objek tersebut merupakan realisasi dari kelas String, maka
objek someString memiliki fungsi dan properti yang merupakan anggota dari kelas String.

Dari completion suggestion yang tersedia pada IntelliJ Idea, kita bisa melihat beberapa
fungsi yang dapat digunakan oleh objek someString. Kita bisa menggunakan
fungsi reverse() untuk membuat urutan huruf disusun secara terbalik,
fungsi toUpperCase() yang dapat membuat huruf menjadi kapital atau
fungsi toLowerCase() yang dapat membuat menjadi huruf kecil.

1. fun main() {
2.     val someString = "Dicoding"
3.     println(someString.reversed())
4.     println(someString.toUpperCase())
5.     println(someString.toLowerCase())
6. }
7.  
8. /*
9. Output:
10. gnidociD
11. DICODING
12. dicoding
13. */

Kita juga dapat mengubah tipe data dengan mengakses fungsi yang tersedia dari sebuah
objek String. 

1. fun main() {
2.     val someString = "123"
3.     val someInt = someString.toInt()
4.     val someOtherString = "12.34"
5.     val someDouble = someOtherString.toDouble()
6.  
7.     println(someInt is Int)
8.     println(someDouble is Double)
9. }
10.  
11. /*
12. Output:
13.  
14. true
15. true
16. */

Hasil dari output kode menunjukan nilai true pada kedua variabel tersebut, yang artinya kita
telah berhasil mengubah suatu tipe data String ke tipe data lainnya dengan menggunakan
fungsi yang terdapat pada objek String.

Mungkin seperti itulah gambaran mengenai objek. Penting digarisbawahi bahwa objek
merupakan realisasi dari sebuah blueprint yang tentunya memiliki properti dan fungsi yang
sama dengan blueprint-nya. Salah satu kegunaan objek adalah untuk mengakses berbagai
properti dan fungsi pada kelas.
Class
Seperti yang telah dijelaskan dalam pembahasan objek, Class merupakan sebuah blueprint.
Di dalam kelas ini kita mendefinisikan sesuatu yang
merupakan attribute ataupun behaviour. Contohnya pada sebuah kelas Kendaraan,
atributnya berupa roda, warna, nomor kendaraan, merk, dll. Sedangkan untuk behaviour nya
yaitu maju, mundur, belok kanan, belok kiri, berhenti. Contoh lainnya pada sebuah
kelas Hewan atributnya berupa nama, berat, umur, termasuk mamalia atau bukan dll.
Sedangkan untuk behaviour-nya bisa makan, tidur, berjalan, dsb. 

Setiap kelas memiliki atribut dan behaviour. Dalam Kotlin attributes lebih sering disebut


dengan properties, sedangkan behaviour sering disebut functions. Properti dalam sebuah
kelas memiliki tipe data. Contoh, untuk properti berat pada kelas Hewan dapat
bertipe Double, nama dapat bertipe String, umur dapat bertipe Int dan indikasi mamalia
dapat bertipe Boolean. Jika kelas Hewan kita representasikan dalam bentuk tabel maka
akan terlihat seperti:

Animal

+ name: String
+ weight: Double
+ age: Int
+ isMammal: Boolean

- eat()
- sleep()

+ merupakan properti
- merupakan fungsi 

Pada pembahasan selanjutnya kita akan mencoba membuat sebuah kelas berdasarkan
bentuk tabel di atas. Namun sebelum kita lanjut ke pembahasan berikutnya, mari kita
tekankan kembali beberapa hal yang sudah kita pelajari:

 Class: Merupakan sebuah blueprint yang terdapat properti dan fungsi di dalamnya


 Properties: Karakteristik dari sebuah kelas, memiliki tipe data.
 Functions: Kemampuan atau aksi dari sebuah kelas.
Membuat Kelas
Untuk mendefinisikan kelas dalam Kotlin, Anda cukup gunakan kata kunci class diikuti
dengan nama kelas yang akan dibuat. Mari kita buat contoh kelas pada Kotlin:

1. class Animal

Sangat mudah bukan? Sekarang kita tambahkan properti dan fungsi pada kelas tersebut.

1. class Animal(val name: String,


2.              val weight: Double,
3.              val age: Int,
4.              val isMammal: Boolean
5. ) {
6.  
7.     fun eat(){
8.         println("$name makan !")
9.     }
10.  
11.     fun sleep() {
12.         println("$name tidur !")
13.     }
14. }

Lalu untuk membuat sebuah objek dari suatu kelas, Anda bisa perhatikan struktur kode
berikut:

1. val nameOfObject = NameOfClass([property1], [property2])

Sama seperti variabel, kita bisa gunakan val atau var, dilanjutkan dengan nama objek yang
akan anda buat. Tanda = menunjukan bahwa kita akan menginisialisasi suatu objek,
kemudian diikuti dengan nama kelas dan tanda kurung. Tanda kurung tersebut menunjukan
bahwa kita membuat sebuah objek baru. Di dalam tanda kurung kita dapat menambahkan
nilai properti sesuai yang dibutuhkan pada primary constructor kelasnya.

Maka jika kita coba membuat objek dari kelas yang sudah kita buat, kodenya akan terlihat
seperti ini:

1. val dicodingCat = Animal("Dicoding Miaw", 4.2, 2,true)

Mari kita coba buat kode secara keseluruhan dengan ditambahkan fungsi cetak untuk
melihat nilai properti dalam objeknya.

1. class Animal(val name: String,


2.              val weight: Double,
3.              val age: Int,
4.              val isMammal: Boolean
5. ) {
6.  
7.     fun eat(){
8.         println("$name makan!")
9.     }
10.  
11.     fun sleep() {
12.         println("$name tidur!")
13.     }
14. }
15.  
16. fun main() {
17.     val dicodingCat = Animal("Dicoding Miaw", 4.2, 2,true)
18.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}" )
19.     dicodingCat.eat()
20.     dicodingCat.sleep()
21. }

Dengan menjalankan program tersebut, maka outputnya sebagai berikut:

Nama: Dicoding Miaw, Berat: 4.2, Umur: 2, mamalia: true


Dicoding Miaw makan!
Dicoding Miaw tidur!
Properties
Sebuah kelas dalam Kotlin tentu memiliki properti. Masing - masing kelas memiliki properti
yang berbeda. Contoh sebelumnya pada kelas Animal, properti yang dimiliki
berupa name, weight, age dan isMammal.

Sama seperti variabel yang sudah kita pelajari pada modul Data Types, properti dapat
dideklarasikan sebagai nilai mutable dengan menggunakan var atau sebagai nilai read-
only dengan menggunakan val. 

Property Accessor
Secara standar ketika properti pada kelas dibuat mutable, maka Kotlin akan menghasilkan
fungsi getter dan setter pada properti tersebut. Jika properti pada sebuah kelas dibuat read-
only, Kotlin hanya akan menghasilkan fungsi getter pada properti tersebut. Namun
sebenarnya Anda bisa membuat fungsi getter dan setter secara manual jika pada kasus
tertentu Anda perlu untuk override fungsi tersebut.

Perhatikan kode berikut:

class Animal{
    var name: String = "Dicoding Miaw"
}

fun main(){
    val dicodingCat = Animal()
    println("Nama: ${dicodingCat.name}" )
    dicodingCat.name = "Goose"
    println("Nama: ${dicodingCat.name}")
}

/*
output:
Nama: Dicoding Miaw
Nama: Goose
*/
Pada kode  ${dicodingCat.name} sebenarnya terjadi proses pemanggilan fungsi getter pada
properti name. Namun kita tidak melakukan override pada fungsi getter  sehingga fungsi
tersebut hanya mengembalikan nilai name saja. Begitu juga pada kode dicodingCat.name =
"Goose" pada kode tersebut terjadi pemanggilan fungsi setter pada properti name. 

Tetapi jika kita melakukan override pada fungsi getter dan juga setter , maka kita dapat


menambahkan kode lain pada fungsi getter sesuai dengan kebutuhan. Mari kita coba
modifikasi kode sebelumnya menjadi:

class Animal{
    var name: String = "Dicoding Miaw"
        get(){
            println("Fungsi Getter terpanggil")
            return field
        }
        set(value){
            println("Fungsi Setter terpanggil")
            field = value
        }
}

fun main(){
    val dicodingCat = Animal()
    println("Nama: ${dicodingCat.name}" )
    dicodingCat.name = "Goose"
    println("Nama: ${dicodingCat.name}")
}

/*
output:
Fungsi Getter terpanggil
Nama: Dicoding Miaw
Fungsi Setter terpanggil
Fungsi Getter terpanggil
Nama: Goose
*/
Urutan pendefinisian fungsi get() dan set() tidaklah penting, kita dapat mendefinisikan
fungsi get() tanpa mendefinisikan fungsi set() dan juga sebaliknya. Yang terpenting
pendeklarasiannya dilakukan setelah mendeklarasikan properti tersebut. Pada fungsi get(),
kita perlu mengembalikan nilai sesuai tipe data dari propertinya atau kita dapat
mengembalikan nilai dari properti itu sendiri dengan menggunakan keyword field.
Sedangkan untuk fungsi set() kita memerlukan sebuah parameter. Ini merupakan sebuah
nilai baru yang nantinya akan dijadikan nilai properti. Pada kode di atas parameter tersebut
ditetapkan dengan nama value.
Property Delegation
Pengelolaan properti kelas baik itu memberikan atau merubah sebuah nilai dapat
didelegasikan kepada kelas lain. Dengan ini kita dapat meminimalisir boilerplate dalam
penulisan getter dan setter (jika properties menggunakan var) pada setiap kelas yang kita
buat. Sebagai contoh, kita memiliki tiga buah kelas yang di dalamnya memiliki satu properti
String. Jika kita ingin menerapkan getter dan setter pada setiap properti kelasnya, maka kita
perlu menuliskan getter dan setter tersebut pada seluruh kelas. Hal tersebut dapat
mengurangi efisiensi dalam menuliskan kode karena terlalu banyak kode yang harus kita
tulis secara berulang. Solusinya, kita perlu membuat sebuah kelas yang memang bertugas
untuk mengatur atau mengelola fungsi getter dan setter untuk sebuah properti kelas. Teknik
tersebut pada Kotlin dinamakan Delegate.

Sebelum mendelegasikan sebuah properti kita perlu membuat kelas delegasi terlebih
dahulu. Mari kita buat sebuah kelas delegasi.

1. import kotlin.reflect.KProperty
2.  
3.  
4. class DelegateName {
5.     private var value: String = "Default"
6.  
7.     operator fun getValue(classRef: Any?, property: KProperty<*>) : String {
8.         println("Fungsi ini sama seperti getter untuk properti ${property.name} pada
class $classRef")
9.         return value
10.     }
11.  
12.     operator fun setValue(classRef: Any?, property: KProperty<*>, newValue: String){
13.         println("Fungsi ini sama seperti setter untuk properti ${property.name} pada
class $classRef")
14.         println("Nilai ${property.name} dari: $value akan berubah menjadi $newValue")
15.         value = newValue
16.     }
17. }

Kemudian untuk mendelegasikan sebuah properti kelas, kita gunakan keyword by dalam


menginisialisasi properti tersebut kemudian diikuti dengan namanya. Perhatikan kode
berikut:

1. class Animal {
2.     var name: String by DelegateName()
3. }

Dengan begitu nilai properti name dikelola melalui kelas DelegateName. Kita dapat


mendelegasikan banyak properti yang terdapat pada banyak kelas kepada satu kelas
Delegate saja. Perhatikan kode berikut untuk untuk lebih jelasnya:

1. class Animal {
2.     var name: String by DelegateName()
3. }
4.  
5. class Person {
6.     var name: String by DelegateName()
7. }
8.  
9. class Hero {
10.     var name: String by DelegateName()
11. }

Mari kita membuat sebuah objek, ubah dan akses nilai propertinya pada setiap kelas,
kemudian jalankan. Maka hasilnya akan seperti pada kode berikut:
1. fun main() {
2.     val animal = Animal()
3.     animal.name = "Dicoding Miaw"
4.     println("Nama Hewan: ${animal.name}")
5.  
6.     val person = Person()
7.     person.name = "Dimas"
8.     println("Nama Orang: ${person.name}")
9.  
10.     val hero = Hero()
11.     hero.name = "Gatotkaca"
12.     println("Nama Pahlawan: ${hero.name}")
13. }
14.  
15. /*
16. output:
17.     Fungsi ini sama seperti setter untuk properti name pada class Animal@17f052a3
18.     Nilai name dari: Default akan berubah menjadi Dicoding Miaw
19.     Fungsi ini sama seperti getter untuk properti name pada class Animal@17f052a3
20.     Nama Hewan: Dicoding Miaw
21.     Fungsi ini sama seperti setter untuk properti name pada class Person@2e0fa5d3
22.     Nilai name dari: Default akan berubah menjadi Dimas
23.     Fungsi ini sama seperti getter untuk properti name pada class Person@2e0fa5d3
24.     Nama Orang: Dimas
25.     Fungsi ini sama seperti setter untuk properti name pada class Hero@5010be6
26.     Nilai name dari: Default akan berubah menjadi Gatotkaca
27.     Fungsi ini sama seperti getter untuk properti name pada class Hero@5010be6
28.     Nama Pahlawan: Gatotkaca
29. */

Pada contoh di atas, delegasi hanya dapat digunakan oleh properti yang memiliki tipe data
String. Namun kita juga dapat membuat sebuah delegasi kelas umum yang dapat digunakan
oleh seluruh tipe data dengan memanfaatkan tipe data Any.

1. class DelegateGenericClass {
2.     private var value: Any = "Default"
3.  
4.     operator fun getValue(classRef: Any, property: KProperty<*>): Any {
5.         println("Fungsi ini sama seperti getter untuk properti ${property.name} pada
class $classRef")
6.         return value
7.     }
8.  
9.     operator fun setValue(classRef: Any, property: KProperty<*>, newValue: Any) {
10.         println("Nilai ${property.name} dari: $value akan berubah menjadi $newValue")
11.         value = newValue
12.     }
13. }
14.  
15. class Animal {
16.     var name: Any by DelegateGenericClass()
17.     var weight: Any by DelegateGenericClass()
18.     var age: Any by DelegateGenericClass()
19. }

Kemudian mari kita membuat sebuah objek dari kelas Animal, ubah dan akses nilai
propertinya kemudian jalankan. Maka hasilnya akan seperti pada kode berikut:

1. fun main(){
2.     val animal = Animal()
3.     animal.name = "Dicoding cat"
4.     animal.weight = 6.2
5.     animal.age = 1
6.  
7.     println("Nama: ${animal.name}")
8.     println("Berat: ${animal.weight}")
9.     println("Umur: ${animal.age} Tahun")
10. }
11.  
12. /*
13. output:
14.     Nilai name dari: Default akan berubah menjadi Dicoding cat
15.     Nilai weight dari: Default akan berubah menjadi 6.2
16.     Nilai age dari: Default akan berubah menjadi 1
17.     Fungsi ini sama seperti getter untuk properti name pada class Animal@17f052a3
18.     Nama: Dicoding cat
19.     Fungsi ini sama seperti getter untuk properti weight pada class Animal@17f052a3
20.     Berat: 6.2
21.     Fungsi ini sama seperti getter untuk properti age pada class Animal@17f052a3
22.     Umur: 1 Tahun
23. */

Perhatikan kode diatas, kita telah memberikan nilai pada setiap properti dengan tipe data
yang berbeda. Tetapi dengan DegelateGenericClass(), pengelolaan properti dapat
digunakan pada seluruh tipe data properti. 
Extension Properties
Pada materi Kotlin Functional Programming kita sudah mengenal bahwa Kotlin dapat
meng-extends sebuah fungsi pada kelas tanpa harus mewarisi kelasnya. Hal ini dilakukan
dengan deklarasi khusus yang disebut dengan Extension.

Extension properties pada Kotlin sama halnya seperti melakukannya pada Extension
function. Kita dapat menambahkan sebuah properti tanpa harus membuat sebuah kelas
yang mewarisi kelas tersebut. Tetapi perlu diingat bahwa properti yang kita buat bukan
benar - benar berada pada kelas. Sebabnya, Extension properties dilakukan di luar kelas.
Dengan demikian, Extension properties hanya bisa didefinisikan dengan cara
menyediakan getter dan/atau setter secara eksplisit. Mari kita buat sebuah Extension
properties pada kelas Animal.

1. class Animal(var name: String, var weight: Double, var age: Int, var isMammal: Boolean)
2.  
3. val Animal.getAnimalInfo : String
4.     get() =  "Nama: ${this.name}, Berat: ${this.weight}, Umur: ${this.age} Mamalia: $
{this.isMammal}"

Dengan menambahkan Extension properties getAnimalInfo pada kelas Animal, maka kita


dapat menggunakan properti tersebut pada sebuah objek kelas Animal.

1. fun main() {
2.     val dicodingCat = Animal("Dicoding Miaw", 5.0, 2, true)
3.     println(dicodingCat.getAnimalInfo)
4. }

Dengan menjalankan kode tersebut maka, output-nya sebagai berikut:

Nama: Dicoding Miaw, Berat: 5.0, Umur: 2 Mammalia: true


Ketika suatu objek dibuat, semua properti pada kelas tersebut harus memiliki nilai. Kita
dapat langsung menginisialisasi pada properti tertentu atau menginisialisasinya
melalui constructor (konstruktor). Konstruktor merupakan fungsi spesial yang digunakan
untuk menginisialisasi properti yang terdapat pada kelas tersebut. 

Terdapat 2 (dua) tipe konstruktor pada Kotlin, yaitu primary constructor dan secondary


constructor. Yuk kita coba mempelajarinya bersama.

Primary Constructor
Seperti namanya, jika kita akan membuat suatu objek dari sebuah kelas dan kelas tersebut
memiliki primary constructor di dalamnya, maka kita diharuskan mengirim nilai sesuai
properti yang dibutuhkan. Penulisan primary constructor mirip seperti parameter pada
fungsi. Properti cukup dituliskan pada header class diawali dengan var atau val. Perhatikan
kode berikut:

1. class Animal(val name: String, val weight: Double, val age: Int, val isMammal: Boolean)

Pada baris kode tersebut kita tidak hanya membuat sebuah kelas, namun sekaligus
menambahkan sebuah primary constructor pada kelas tersebut. Sekarang Mari kita coba
membuat objek dari kelas tersebut:

1. fun main(){
2.     val dicodingCat = Animal("Dicoding Miaw", 4.2, 2, true)
3.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}" )
4. }
5.  
6. /*
7. output:
8.     Nama: Dicoding Miaw, Berat: 4.2, Umur: 2, mamalia: true
9. */

Perhatikan kode di atas. Karena kelas Animal memiliki primary constructor, maka saat


membuat objeknya kita perlu mengirimkan beberapa nilai
yaitu name, weight, age dan isMammal.

Primary constructor juga dapat memiliki nilai default, dengan begitu jika kita tidak
menetapkan nilai untuk parameter tersebut maka properti tersebut akan memiliki
nilai default. Contohnya, kita bisa memberikan nilai default terhadap properti age. Sehingga
ketika pembuatan objek, pengiriman nilai age pada primary constructor bersifat opsional. 

Untuk membuat nilai default pada sebuah primary constructor, kita perlu menginisialisasi


nilai pada saat kita menuliskan properti pada kelas. Perhatikan kode berikut:

1. class Animal(var name: String, var weight: Double, var age: Int = 0, var isMammal:
Boolean = true)

Kode tersebut menunjukan bahwa kita membuat nilai default pada properti age yang


bernilai 0 dan isMammal yang bernilai true. Sehingga pada pembuatan objek Animal, kita
bisa mengirimkan nilai name dan weight saja pada primary constructor. Mari kita coba
membuat objek dengan memanfaatkan nilai default pada konstruktor.
1. fun main(){
2.     val dicodingCat = Animal("Dicoding Miaw", 4.2)
3.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}" )
4. }
5.  
6. /*
7.  output:
8.     Nama: Dicoding Miaw, Berat: 4.2, Umur: 0, mamalia: true
9. */

Hasil dari kode tersebut memperlihatkan bahwa properti age dan isMammal memiliki


nilai default. Sekali lagi, properti tersebut bersifat opsional, dengan begitu kita tetap dapat
mengirimkan nilai pada properti walaupun telah memiliki nilai default. 

Kita juga dapat secara eksplisit memilih properti yang ingin kita berikan nilai dengan
menambahkan nama properti dan tanda = sebelum mengisikan nilai properti.

1. val dicodingCat = Animal("Dicoding Miaw", 4.2, isMammal =  true)

Init block
Kotlin menyediakan blok init yang memungkinkan kita untuk menuliskan properti di
dalam body class ketika kita menggunakan primary constructor. Memang, memiliki kode
banyak di dalam body class bukanlah hal yang baik. Kotlin bertujuan agar kita dapat
menuliskan kode seminimal mungkin. Tapi blok init di sini memiliki beberapa fungsi selain
menginisialisasi properti kelas.  satu fungsi lainnya adalah untuk membantu dalam
memvalidasi sebuah nilai properti sebelum diinisialisasi. Pada kelas Animal contohnya, kita
dapat melakukan verifikasi bahwa berat dan umur hewan tidak boleh bernilai kurang dari
nol.

Untuk membuatnya, kita dapat menggunakan keyword init kemudian inisialisasikan semua


properti di dalam blok tersebut dengan parameter kelas:

1. class Animal(pName: String, pWeight: Double, pAge: Int, pIsMammal: Boolean){


2.     val name: String
3.     val weight: Double
4.     val age: Int
5.     val isMammal: Boolean
6.  
7.     init {
8.         weight = if(pWeight < 0) 0.1 else pWeight
9.         age = if(pAge < 0) 0 else pAge
10.         name = pName
11.         isMammal = pIsMammal
12.     }
13. }

Primary constructor dan init harus saling terhubung. Fungsi init dijalankan ketika suatu objek


dibuat dengan menggunakan primary constructor. Mari kita coba untuk membuatnya.

1. fun main() {
2.     val dicodingCat = Animal("Dicoding Miaw", 4.2, 2, true)
3.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}")
4. }
5.  
6. /*
7. output:
8.     Nama: Dicoding Miaw, Berat: 4.2, Umur: 2, mamalia: true
9. */

Perhatikan juga penamaan antara properti pada body class dan parameter pada head


class penamaan antara keduanya harus berbeda agar tidak terjadi ambiguitas. Lantas
bagaimana jika kita ingin penamaan keduanya sama? Untuk menghindari ambiguitas kita
dapat menggunakan keyword this dalam menginisialisasi properti tersebut dalam blok init.

1. class Animal(name: String, weight: Double, age: Int, isMammal: Boolean) {


2.     val name: String
3.     val weight: Double
4.     val age: Int
5.     val isMammal: Boolean
6.  
7.     init {
8.         this.weight = if(weight < 0) 0.1 else weight
9.         this.age = if(age < 0) 0  else age
10.         this.name = name
11.         this.isMammal = isMammal
12.     }
13. }

Kata kunci this tersebut merujuk kepada suatu kelas dimana jika kita menggunakannya
diikuti dengan nama properti maka kita menunjuk pada properti yang terdapat pada kelas
tersebut. Dengan begitu, tidak akan ada ambiguitas walaupun kita menggunakan penamaan
yang sama antara properti dan parameter primary constructor.
Secondary Constructor
Secondary constructor digunakan ketika kita ingin menginisialisasi sebuah kelas dengan
cara yang lain. Anda dapat membuat lebih dari satu secondary constructor. Sebagai contoh,
kita bisa menambahkan secondary constructor pada kelas Animal:

1. class Animal(name: String, weight: Double, age: Int) {


2.     val name: String
3.     val weight: Double
4.     val age: Int
5.     var isMammal: Boolean
6.  
7.     init {
8.         this.weight = if(weight < 0) 0.1 else weight
9.         this.age = if(age < 0) 0  else age
10.         this.name = name
11.         this.isMammal = false
12.     }
13.  
14.     constructor(name: String, weight: Double, age: Int, isMammal: Boolean) : this(name,
weight, age) {
15.         this.isMammal = isMammal
16.     }
17. }
18.  
19. fun main() {
20.     val dicodingCat = Animal("Dicoding Miaw", 2.5, 2, true)
21.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}")
22.  
23.     val dicodingBird = Animal("Dicoding tweet", 0.5, 1)
24.     println("Nama: ${dicodingBird.name}, Berat: ${dicodingBird.weight}, Umur: $
{dicodingBird.age}, mamalia: ${dicodingBird.isMammal}")
25. }
26.  
27. /*
28. output:
29.     Nama: Dicoding Miaw, Berat: 2.5, Umur: 2, mamalia: true
30.     Nama: Dicoding tweet, Berat: 0.5, Umur: 1, mamalia: false
31. */

Dengan begitu, objek Animal dapat diinisialisasi dengan secondary constructor ketika


nilai name, weight, age dan isMammal tersedia. Tetapi jika nilai isMammal tidak tersedia,
primary constructor lah yang akan digunakan dan nilai isMammal dapat diinisialisasi pada
blok init dengan nilai default.

Default Constructor
Kotlin secara otomatis membuat sebuah default constructor pada kelas jika kita tidak
membuat sebuah konstruktor secara manual. Perhatikan kode berikut:

1. class Animal{
2.     val name: String = "Dicoding Miaw"
3.     val weight: Double = 4.2
4.     val age: Int = 2
5.     val isMammal: Boolean = true
6. }
7.  
8. fun main(){
9.     val dicodingCat = Animal()
10.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}" )
11. }
12.  
13. /*
14. output:
15.     Nama: Dicoding Miaw, Berat: 4.2, Umur: 2, mamalia: true
16. */

Ketika kita membuat sebuah objek, default konstruktor akan dipanggil. Konstruktor tersebut


akan menginisialisasi properti yang terdapat pada kelas dengan nilai default.
Visibility Modifiers
Kali ini kita akan mengenal beberapa tentang visibility modifiers atau hak akses Pada Kotlin.
Tentunya, tidak semua properti dan fungsi pada sebuah kelas memiliki hak akses publik.
Ada beberapa yang hanya dapat diakses dari dalam dan ada yang dapat diakses dari luar
kelasnya. Dengan menentukan hak akses tersebut, kita dapat membatasi akses data pada
sebuah kelas. Berikut macam - macam hak akses dan penjelasan singkatnya yang dapat
digunakan pada Kotlin:

 Public: Hak akses yang cakupannya paling luas. Anggota yang diberi modifier ini
dapat diakses dari manapun.
 Private: Hak akses yang cakupannya paling terbatas. Anggota yang menerapkannya
hanya dapat diakses pada scope yang sama.
 Protected: Hak akses yang cakupannya terbatas pada hirarki kelas. Anggota hanya
dapat diakses pada kelas turunannya atau kelas itu sendiri.
 Internal: Hak akses yang cakupannya terbatas pada satu modul. Anggota yang
menggunakannya tidak dapat diakses diluar dari modulnya tersebut.
Semua modifier tersebut bisa digunakan untuk kelas, objek, konstruktor, fungsi, beserta
properti yang ada di dalamnya. Kecuali modifier protected yang hanya bisa digunakan untuk
anggota di dalam sebuah kelas dan interface. Protected tidak bisa digunakan
pada package member seperti kelas, objek, dan yang lainnya. Setelah mengetahui
pentingnya hak akses, selanjutnya kita akan membahas bagaimana kita menentukan hak
akses public, private, protected dan internal pada Kotlin.

Public
Berbeda dengan bahasa pemrograman umumnya, default modifier pada Kotlin
adalah public. Ketika sebuah anggota memiliki hak akses public maka anggota tersebut
dapat diakses dari luar kelasnya melalui sebuah objek kelas tersebut.

Pada pembahasan sebelumnya kita sudah memiliki sebuah kelas Animal dengan properti


publik seperti name, age, weight dan isMammal. Properti tersebut dapat kita akses dari luar
kelas Animal.
Dari completion suggestion terlihat bahwa properti tersebut dapat kita akses di luar dari
kelasnya. 

Private
Ketika suatu anggota memiliki hak akses private, maka anggota tersebut tidak dapat diakses
dari luar scope-nya. Untuk menggunakan modifier private kita perlu
menambahkan keyword private seperti contoh berikut:

1. private var name: String

Mari kita coba ubah hak akses pada seluruh properti kelas Animal menjadi private.

1. class Animal(private val name: String, private val weight: Double, private val age: Int,
private val isMammal: Boolean)
2.  
3. fun main() {
4.     val dicodingCat = Animal("Dicoding Miaw", 2.5, 2)
5.     println("Nama: ${dicodingCat.name}, Berat: ${dicodingCat.weight}, Umur: $
{dicodingCat.age}, mamalia: ${dicodingCat.isMammal}")
6. }

Dengan menggunakan hak akses private, maka kita tidak diizinkan untuk mengakses
properti pada kelas Animal tersebut  dari luar kelasnya. Anda akan berjumpa
dengan eror Cannot access '[PROPERTY]': it is private in 'Animal'. Satu satunya cara untuk
mengakses properti private dari sebuah kelas adalah dengan menambahkan
fungsi getter dan setter secara manual. Fungsi getter dan setter sebenarnya dihasilkan
secara otomatis oleh Kotlin ketika properti tersebut memiliki hak akses public tetapi tidak
untuk private. Untuk penulisan getter dan setter pada hak akses private sama seperti fungsi
pada umumnya:

1. fun getName() : String {


2.     return name
3. }
4.  
5. fun setName(newName: String) {
6.     name = newName
7. }

Fungsi getName() bertujuan untuk mengembalikan nilai name yang memiliki tipe data String.


Kemudian fungsi setName() bertujuan untuk mengubah nilai properti name dengan nilai
baru. Fungsi setName() membutuhkan satu parameter bertipe String yang nantinya akan
dimasukkan nilainya ke dalam properti name.

Mari kita coba menerapkannya.

1. class Animal(private var name: String, private val weight: Double, private val age: Int,
private val isMammal: Boolean = true) {
2.  
3.     fun getName() : String {
4.         return name
5.     }
6.  
7.     fun setName(newName: String) {
8.         name = newName
9.     }
10.  
11. }
12.  
13. fun main() {
14.     val dicodingCat = Animal("Dicoding Miaw", 2.5, 2)
15.     println(dicodingCat.getName())
16.     dicodingCat.setName("Gooose")
17.     println(dicodingCat.getName())
18. }
19.  
20. /*
21. output:
22.     Dicoding Miaw
23.     Gooose
24. */

Pada kode di atas, terlihat bahwa kita berhasil mengubah nilai properti name dari nilai awal
yang kita inisialisasikan pada konstruktor. Ia menjadi nilai baru yang kita tentukan dengan
menggunakan fungsi setName(). 

Protected
Hak akses protected mirip seperti private, namun pembatasannya lebih luas dalam sebuah
hirarki kelas. Hak akses protected digunakan ketika kita menginginkan sebuah anggota dari
induk kelas dapat diakses hanya oleh kelas yang merupakan turunannya. Perhatikan kode
di bawah ini untuk contoh penggunaan hak akses protected.

1. open class Animal(val name: String, protected val weight: Double)


2.  
3. class Cat(pName: String, pWeight: Double) : Animal(pName, pWeight)

Pada kode tersebut, properti weight pada kelas Animal memiliki hak akses protected. Kita


tetap bisa mengaksesnya dari kelas Cat yang termasuk dalam hirarki kelas Animal. Namun
kita tidak dapat mengakses properti tersebut secara langsung dari luar hirarki kelasnya. Eror
akan terjadi jika kita melakukan hal tersebut.

1. fun main() {
2.     val cat = Cat("Dicoding Miaw", 2.0)
3.     println("Nama Kucing: ${cat.name}")
4.     println("Berat Kucing: ${cat.weight}") //Cannot access 'weight': it is protected in
'Cat'
5. }

Internal
Internal merupakan hak akses baru yang diperkenalkan pada Kotlin. Hak akses ini
membatasi suatu anggota untuk dapat diakses hanya pada satu modul. Berikut ini contoh
penggunaan hak akses internal:

1. internal class Animal(val name: String)

Pada contoh di atas, kelas Animal telah ditetapkan sebagai kelas internal, maka kelas
tersebut hanya dapat diakses dari modul yang sama. Hak akses ini sangat berguna ketika
kita mengembangkan sebuah aplikasi yang memiliki beberapa modul di dalamnya.
Inheritances
Dalam gambaran dunia nyata, banyak objek yang berbeda tetapi punya kesamaan atau
kemiripan tertentu. Contohnya Kucing dan Kambing memiliki banyak kesamaan karena
objek tersebut merupakan hewan. Kucing merupakan hewan mamalia, begitu juga dengan
kambing. Mungkin yang membedakan objek tersebut adalah cara mereka mencari makanan
dan jenis makanan yang dimakan. Sama halnya pada OOP, beberapa objek yang berbeda
bisa saja memiliki kesamaan dalam hal tertentu. Di situlah
konsep inheritance atau pewarisan harus diterapkan. Pewarisan dapat mencegah kita
melakukan perulangan kode. Untuk lebih memahaminya lihatlah contoh bagan pada sebuah
kelas berikut:

Cat Fish Snake

+ name: String + name: String + name: String

+ furColor: String + scaleColor: String + skinColor: String

+ weight: Double + weight: Double + weight: Double

+ age: Integer + age: Integer + age: Integer

+ numberOfFeet: Integer + numberOfFin: Integer + isToxic: Boolean

+ isCarnivore: Boolean + isCarnivore: Boolean + isCarnivore: Boolean


- eat() - eat() - eat()

- sleep() - sleep() - sleep()

- playWithHuman() - swim() - bite()


Pada bagan tersebut dapat kita lihat pada kelas Cat, Fish dan Snake memiliki beberapa
properti yang sama seperti name, weight, age, isCarnivore dan juga memiliki beberapa
fungsi yang sama seperti eat() dan sleep().  Jika kita ubah diagram kelas Kucing menjadi
sebuah kode maka akan menjadi seperti ini:

1. class Cat(val name: String, val furColor: String, val weight: Double, val age: Integer,
val numberOfFeet: Integer, val isCarnivore: Boolean) {
2.     fun eat(){
3.         println("$name sedang makan!")
4.     }
5.  
6.     fun sleep() {
7.         println("$name sedang tidur!")
8.     }
9.  
10.     fun playWithHuman() {
11.         println("$name bermain dengan Manusia!")
12.     }
13. }

Tidak ada masalah dengan kode tersebut, tetapi ketika kita akan membuat kelas dari
diagram lainnya contohnya Fish maka kita harus menuliskan ulang properti
seperti name, weight, age dan properti atau fungsi yang sama lainnya. Hal ini dapat
mengurangi efisiensi dalam menuliskan kode. 
Dengan teknik inheritance, kita bisa mengelompokkan properti dan fungsi yang sama.
Caranya , buat sebuah kelas baru yang nantinya akan diturunkan sifatnya pada sebuah
kelas:

Animal

+ name: String

+ weight: Double

+ age: Integer

+ isCarnivore: Boolean
- eat()

- sleep()
Cat Fish Snake

+ furColor: String + scaleColor: String + skinColor: String

+ numberOfFeet: Integer + numberOfFin: Integer + isToxic: Boolean


playWithHuman() swim() - bite()

Ketika kelas Animal telah dibuat, kelas lainnya dapat melakukan extends pada kelas


tersebut. Dalam pewarisan, kelas Animal (main class) dapat disebut
sebagai super atau parent class. Kelas yang melakukan extends pada kelas tersebut
disebut child class. Dalam Kotlin untuk melakukan extends pada sebuah kelas dapat
dilakukan dengan tanda : seperti contoh berikut:

1. class ChildClass : ParentClass {


2.  
3. }

Mari kita buat kelas Animal yang akan berperan sebagai parent class seperti berikut: 

1. open class Animal(val name: String, val weight: Double, val age: Int, val isCarnivore:
Boolean){
2.  
3.     open fun eat(){
4.         println("$name sedang makan!")
5.     }
6.  
7.     open fun sleep(){
8.         println("$name sedang tidur!")
9.     }
10. }

Untuk membuat sebuah super atau parent class kita akan membutuhkan open class. Kelas


pada Kotlin secara default bersifat final, oleh karena itu kita harus mengubahnya
menjadi open class sebelum melakukan extends kelas tersebut. 

Ubahlah kelas Cat dengan melakukan extends pada kelas Animal seperti berikut:

1. class Cat(pName: String, pWeight: Double, pAge: Int, pIsCarnivore: Boolean, val
furColor: String, val numberOfFeet: Int)
2.     : Animal(pName, pWeight, pAge, pIsCarnivore) {
3.  
4.     fun playWithHuman() {
5.         println("$name bermain bersama Manusia !")
6.     }
7.  
8.     override fun eat(){
9.         println("$name sedang memakan ikan !")
10.     }
11.  
12.     override fun sleep() {
13.         println("$name sedang tidur di bantal !")
14.     }
15. }

Dengan begitu, selain fungsi yang terdapat di dalamnya, kelas Cat juga dapat mengakses
seluruh fungsi dan properti yang terdapat kelas Animal.

1. fun main(){
2.     val dicodingCat = Cat("Dicoding Miaw", 3.2, 2, true, "Brown", 4)
3.  
4.     dicodingCat.playWithHuman()
5.     dicodingCat.eat()
6.     dicodingCat.sleep()
7. }
8.  
9. /*
10. output:
11.     Dicoding Miaw bermain bersama Manusia !
12.     Dicoding Miaw sedang memakan ikan !
13.     Dicoding Miaw sedang tidur di bantal !
14. */
Overloading
Pada Kotlin menggunakan dua atau lebih fungsi dengan nama yang sama disebut
dengan overloading. Overloading dapat dilakukan selama fungsi itu memiliki parameter yang
berbeda. Berikut merupakan contoh overloading fungsi eat() pada sebuah kelas Animal.

1. class Animal(private var name: String) {


2.     fun eat() {
3.         println("$name makan!")
4.     }
5.  
6.     fun eat(typeFood: String) {
7.         println("$name memakan $typeFood!")
8.     }
9.  
10.     fun eat(typeFood: String, quantity: Double) {
11.         println("$name memakan $typeFood sebanyak $quantity grams!")
12.     }
13.  
14.     fun sleep() {
15.         println("$name tidur!")
16.     }
17. }

Pada kelas Animal terdapat beberapa fungsi dengan penamaan yang sama, tetapi tidak
menyebabkan eror. Sebabnya, fungsi tersebut memiliki parameter yang berbeda sehingga
tidak akan terjadi ambiguitas dalam penggunaan fungsi tersebut. Mari kita coba buat sebuah
objek dari kelas tersebut dan mengakses fungsinya satu persatu.

1. fun main() {
2.     val dicodingCat = Animal("Dicoding Miaw")
3.  
4.     dicodingCat.eat()
5.     dicodingCat.eat("Ikan Tuna")
6.     dicodingCat.eat("Ikan Tuna", 450.0)
7. }

Fungsi eat() yang pertama dapat digunakan tanpa mengirimkan parameter apapun.


Sedangkan fungsi eat() yang kedua kita mengirimkan sebuah parameter String sebagai
nilai typeFood. Dan fungsi eat() yang terakhir membutuhkan 2 (dua) buah
parameter, typeFood dan  quantity. 

Overloading pada fungsi merupakan sebuah fitur yang sangat powerful. Untuk dapat lebih
memahami betapa pentingnya overloading, mari kita buat sebuah kelas Calculator yang di
dalamnya memiliki fungsi matematika dengan menerapkan overloading pada sebuah fungsi.

1. class Calculator {
2.     fun add(value1: Int, value2: Int) = value1 + value2
3.     fun add(value1: Int, value2: Int, value3: Int) = value1 + value2 + value3
4.     fun add(value1: Double, value2: Double) = value1 + value2
5.     fun add(value1: Float, value2: Float) = value1 + value2
6.  
7.     fun min(value1: Int, value2: Int) = if (value1 < value2) value1 else value2
8.     fun min(value1: Double, value2: Double) = if (value1 < value2) value1 else value2
9. }

Kemudian kita buat sebuah objek Calculator pada main() dan mengakses fungsi yang


berada pada kelas tersebut.

1. fun main() {
2.     val calc = Calculator()
3.  
4.     println(calc.add(2, 4))
5.     println(calc.add(2.5, 2.2))
6.     println(calc.add(6f, 7f))
7.     println(calc.add(1, 2, 3))
8.  
9.     println(calc.min(9, 2))
10.     println(calc.min(17.2, 18.3))
11. }
12.  
13. /*
14. output
15.     6
16.     4.7
17.     13.0
18.     6
19.     2
20.     17.2
21. */

Pada contoh yang kita buat, fungsi add(2, 4) memanggil fungsi add yang memiliki


parameter Integer, fungsi add(2.5, 2.2) memanggil fungsi add yang memiliki
parameter Double, begitu juga dengan yang lainnya.
Abstract Class
Seperti namanya, abstract merupakan gambaran umum dari sebuah kelas. Ia tidak dapat
direalisasikan dalam sebuah objek. Pada modul sebelumnya kita sudah mempunyai
kelas Animal. Secara harfiah hewan merupakan sebuah sifat. Kita tidak tahu bagaimana
objek hewan tersebut. Kita tahu bentuk kucing, ikan dan ular seperti apa tetapi tidak untuk
hewan. Maka dari itu konsep abstract class perlu diterapkan agar kelas Animal tidak dapat
direalisasikan dalam bentuk objek namun tetap dapat menurunkan sifatnya kepada child
class-nya.

Untuk menjadikan sebuah kelas abstract, kita hanya perlu


menambahkan keyword abstract sebelum menuliskan nama kelas:

1. abstract class Animal(var name: String, var weight: Double, var age: Int, var
isCarnivore: Boolean){
2.  
3.     fun eat(){
4.         println("$name sedang makan !")
5.     }
6.  
7.     fun sleep(){
8.         println("$name sedang tidur !")
9.     }
10. }

Dengan begitu kelas Animal tidak dapat kita inisialisasikan menjadi sebuah objek. 

1. fun main(){
2.     val animal = Animal("dicoding animal", 2.6, 1, true)
3. }

Ketika kita mencoba membuat objek dari kelas Animal, akan terdapat eror berikut:

Cannot create an instance of an abstract class


Interfaces
Interfaces merupakan suatu konsep sifat umum yang nantinya digunakan oleh suatu kelas
agar dapat memiliki sifat tersebut. Interface sangat mirip dengan abstract class, namun
tanpa sebuah properti deklarasi dan fungsi yang dideklarasikan tanpa isi. Tujuan dari
interface ini hanya untuk diimplementasikan oleh sebuah kelas. Kelas yang
mengimplementasikan sebuah interface diharuskan melakukan override seluruh properti dan
fungsi sekaligus mendefinisikan isi fungsi yang terdapat pada interfaces-nya.

Cara pembuatan sebuah interface mirip dengan membuat kelas. Pada umumnya penamaan
sebuah interface dituliskan dengan awalan huruf I kapital. Hal ini tidak diharuskan secara
sintaks tapi ini merupakan penerapan terbaik dalam penamaan sebuah interface. Tujuannya
agar dapat mudah membedakannya dengan kelas. Berikut merupakan contoh pembuatan
sebuah interface:

1. interface IFly {
2.     fun fly()
3. }

Cara mengimplementasikan sebuah interface pada kelas, sama seperti kita


melakukan extends pada sebuah super atau parent class. Untuk lebih jelasnya, mari kita
buat sebuah kelas burung dengan mengimplementasikan interface IFly:

1. class Bird : IFly {


2.  
3. }

Kita akan mendapati eror ketika selesai menuliskan kode tersebut. Pesan eror tersebut
mengatakan “class Bird is not abstract and does not implement abstract member”.
Maksud dari eror tersebut adalah kita harus  mengimplementasi sebuah abstract
member yang pada kasus ini adalah sebuah fungsi abstract yang terdapat pada IFly. Maka
untuk menghilangkan eror tersebut, kita harus melakukan override fungsi yang terdapat
pada IFly.

1. class Bird : IFly {


2.     override fun fly() {
3.         println("I flying without wings")
4.     }
5. }

Untuk menambahkan sebuah properti pada interface, kita cukup menuliskannya seperti
pada kelas namun tanpa melakukan inisialisasi nilai:

1. interface IFly {
2.     fun fly()
3.     val numberOfWings: Int
4. }

Sama seperti fungsi, kita juga diharuskan melakukan override properti. Overriding properti


bisa dilakukan pada sebuah konstruktor kelas seperti berikut:

1. class Bird(override val numberOfWings: Int) : IFly {


2.  
3.     override fun fly() {
4.         if(numberOfWings > 0) println("Flying with $numberOfWings wings")
5.         else println("I'm Flying without wings")
6.     }
7. }
Import dan Packages
Seluruh konten pada Kotlin, seperti kelas dan fungsi, dibungkus dalam
sebuah package. Package tersebut digunakan untuk mengelompokkan kelas, fungsi dan
variabel yang mempunyai kemiripan fungsionalitas. Untuk menggunakan kelas, fungsi
maupun variabel yang berada pada suatu package, kita harus menuliskan secara lengkap
alamat package tersebut. Sebagai contoh kita akan menggunakan kelas Random, maka kita
harus menuliskan seperti ini:

1. val someInt = kotlin.random.Random(0).nextInt(1, 10)

Kode tersebut menunjukkan bahwa kelas Random berada pada package kotlin.random,


tetapi apakah perlu menuliskan kode sepanjang itu untuk menggunakan sebuah kelas?
Tentu tidak, untuk meminimalisir hal tersebut kita cukup mengimpor package kelas Random.
Dengan begitu kita tidak perlu menuliskan kode yang panjang secara berulang.

Importing Package
Untuk mengimpor suatu package kelas, fungsi atau variabel, kita cukup
menuliskan keyword import kemudian dilanjutkan dengan alamat spesifiknya seperti: 

1. import packagename.ClassName
2. import packagename.functionName
3. import packagename.propertyName

Karena kelas Random berada pada package kotlin.random, maka penulisannya menjadi


seperti ini:

1. import kotlin.random.Random

Setelah kita impor kelas Random beserta alamat package-nya, kita dapat menuliskan


kelas Random secara langsung tanpa menulis seluruh alamat package-nya. Tentunya hal ini
akan membuat waktu dalam menuliskan kode lebih efisien.

1. import kotlin.random.Random
2.  
3. val someInt = Random(0).nextInt(1, 10)

Biasanya terdapat banyak kelas, fungsi ataupun variabel dalam sebuah package.


Contohnya kita akan menggunakan beberapa fungsi dan variabel matematika
pada package kotlin.math seperti berikut:

1. import kotlin.math.PI
2. import kotlin.math.cos
3. import kotlin.math.sqrt
4.  
5. fun main(){
6.     println(PI)
7.     println(cos(120.0))
8.     println(sqrt(9.0))
9. }
10.  
11. /*
12. Output:
13.     3.141592653589793
14.     0.8141809705265618
15.     3.0
16. */
Kita juga dapat mengganti nama sebuah kelas, fungsi atau variabel yang kita import dengan
menggunakan alias yang direpresentasikan dengan kata kunci as.

1. import kotlin.math.PI
2. import kotlin.math.cos as cosinus
3. import kotlin.math.sqrt as akar
4.  
5. fun main(){
6.     println(PI)
7.     println(cosinus(120.0))
8.     println(akar(9.0))
9. }
10.  
11. /*
12. Output:
13.     3.141592653589793
14.     0.8141809705265618
15.     3.0
16. */

Biasanya as digunakan ketika kita menggunakan sebuah kelas, fungsi, maupun variabel


yang memiliki nama yang sama namun berbeda package-nya. Ini bertujuan untuk
menghindari ambiguitas.

Seperti yang kita ketahui sebelumnya, pada package kotlin.math terdapat banyak fungsi


dan variabel yang dapat kita gunakan. Kita bisa melihat pada completion suggestion berikut:

Kita dapat mengimpor seluruh kelas, fungsi dan variabel yang berada pada
suatu package dengan menggunakan tanda  * pada akhir package tersebut.

1. import kotlin.math.*
2.  
3. fun main(){
4.     println(PI)
5.     println(cos(120.0))
6.     println(sqrt(9.0))
7. }
8.  
9.  
10. /*
11. Output:
12.     3.141592653589793
13.     0.8141809705265618
14.     3.0
15. */
Membuat Package Baru
Seperti yang diketahui sebelumnya, package merupakan pembungkus dari kelas (package-
level class), fungsi (package-level function) atau variabel (package-level variable) berfungsi
serupa. Kita juga sudah mengetahui cara mengimpor suatu kelas, fungsi atau variabel yang
terdapat pada sebuah package. Namun kita belum tahu bagaimana package tersebut
dibuat. Jadi pada pembahasan kali ini kita akan mencoba bagaimana untuk membuat
sebuah package pada Kotlin.

Idealnya sebuah package pada Kotlin dituliskan dengan awalan nama domain perusahaan


yang dibalik. Contoh, com.dicoding. Kemudian diikuti dengan nama package yang akan
digunakan.

Untuk membuat sebuah package kita perlu membuat folder package pada berkas proyek.


Perhatikan Project Tool Window yang terdapat pada IntelliJ Idea. Klik kanan pada
folder src kemudian arahkan pada menu New > package.

Setelah itu ketikkan nama package yang akan kita buat, misalnya com.dicoding.oop.utils:


Perlu diingat, penamaan package selalu dituliskan dengan flatcase, tanpa garis bawah dan
dipisahkan dengan titik.

Dengan menekan tombol “OK” maka kita berhasil membuat sebuah package folder pada
proyek aplikasi kita. Maka struktur proyek akan menjadi seperti ini:

Selanjutnya, buatlah sebuah berkas di dalam package utils. Disini kita menamai berkas


tersebut dengan nama MyMath.kt. Bukalah berkas tersebut dan perhatikan baris kode yang
dihasilkan oleh IntelliJ Idea. Seharusnya kita melihat baris kode berikut pada
berkas MyMath.kt.

1. package com.dicoding.oop.utils
2. fun sayHello() = println("Hello From package com.dicoding.oop.utils")

Kita sudah membuat sebuah fungsi sayHello() pada package-level. Untuk mengakses fungsi


tersebut kita dapat menuliskan secara eksplisit alamat package. Buatlah sebuah berkas
Kotlin dengan nama Main.kt pada package com.dicoding.oop kemudian akses
fungsi sayHello() yang terdapat pada package com.dicoding.oop.utils

1. fun main(){
2.     com.dicoding.oop.utils.sayHello()
3. }
4.  
5. /*
6. Output:
7.     Hello From package com.dicoding.oop.utils
8. */

Atau kita dapat menggunakan fungsi tersebut dengan mengimpor package-level


function tersebut.
1. package com.dicoding.oop
2.  
3. import com.dicoding.oop.utils.sayHello
4.  
5. fun main() {
6.     sayHello()
7. }
8.  
9. /*
10. Output:
11.     Hello From package com.dicoding.oop.utils
12. */

Untuk dapat memahami tentang package lebih lanjut, mari kita buat beberapa fungsi dan
variabel pada package tersebut. Buka kembali berkas MyMath.kt, tambahkan beberapa
fungsi dan variabel yang akan kita gunakan nantinya.

1. package com.dicoding.oop.utils
2.  
3. fun sayHello() = println("Hello From package utils")
4.  
5. const val PI = 3.1415926535  // package level variable
6.  
7. fun pow(number: Double, power: Double) : Double {
8.     var result = 1.0
9.     var counter = power
10.     while (counter > 0) {
11.         result *= number
12.         counter--
13.     }
14.     return result
15. }
16.  
17. fun factorial(number: Double) : Double {
18.     var result = 1.0
19.     var counter = 1.0
20.     while (counter <= number) {
21.         result *= counter
22.         counter++
23.     }
24.  
25.     return result
26. }
27.  
28. fun areaOfCircle(radius: Double) : Double {
29.     return PI * 2 * radius
30. }

Panggil beberapa fungsi dan variabel yang sudah ditambahkan pada MyMath.kt.

1. package com.dicoding.oop
2.  
3. import com.dicoding.oop.utils.PI
4. import com.dicoding.oop.utils.factorial
5. import com.dicoding.oop.utils.pow
6. import com.dicoding.oop.utils.sayHello
7.  
8. fun main() {
9.     sayHello()
10.     println(factorial(4.0))
11.     println(pow(3.0, 2.0))
12.     println(PI)
13. }
14.  
15. /*
16. output:
17.     Hello From package com.dicoding.oop.utils
18.     24.0
19.     9.0
20.     3.1415926535
21. */

Pada awal kode terlihat saat kita menggunakan suatu fungsi atau variabel yang berada pada
package tertentu, kita perlu melakukan impor pada setiap fungsi atau variabelnya. Tetapi
jika kita menggunakan seluruh fungsi atau variabel dalam package tertentu kita bisa
menggunakan tanda bintang (*) untuk melakukan impor pada seluruh fungsi dan variabel
di package tersebut. Perhatikan kode berikut:

1. package com.dicoding.oop
2.  
3. import com.dicoding.oop.utils.*
4.  
5. fun main() {
6.     sayHello()
7.     println(factorial(4.0))
8.     println(pow(3.0, 2.0))
9.     println(PI)
10.     println(areaOfCircle(13.0))
11. }
12.  
13. /*
14. output:
15.     Hello From package com.dicoding.oop.utils
16.     24.0
17.     9.0
18.     3.1415926535
19.     81.681408991
20. */

Dengan memanggil fungsi areaOfCircle() maka kita menggunakan seluruh fungsi dan


variabel yang berada pada package com.dicoding.oop.utils,
sehingga import package cukup dilakukan dengan menggunakan tanda bintang (*).
Kode yang baik yaitu kode yang terhindar dari segala bentuk kejadian dengan efek buruk
pada aplikasi kita. Kejadian tersebut pada programming disebut Exception. Hal terburuk
yang disebabkan oleh exception ini adalah dapat terhentinya aplikasi ketika dijalankan. Hal
seperti ini seharusnya kita hindari. Nah karena itu kita harus mengetahui cara menangani
suatu exception (Exception Handling) pada modul selanjutnya.

Apa itu Exception?


Exception adalah event (kejadian) yang dapat mengacaukan jalannya suatu program. Pada
Kotlin semua exception bersifat Unchecked, yang artinya exception terjadi karena
kesalahan pada kode kita. Berikut ini beberapa contoh Unchecked Exception yang sering
mengganggu jalannya program kita:

 ArithmeticException
 NumberFormatException
 NullPointerException
ArithmeticException merupakan exception yang terjadi karena kita membagi suatu
bilangan dengan nilai nol. Berikut merupakan contoh kode yang dapat
membangkitkan ArithmeticException.

1. fun main() {
2.     val someValue = 6
3.     println(someValue / 0)
4. }
5.  
6. /*
7. output:
8.     Exception in thread "main" java.lang.ArithmeticException: / by zero
9. */

NumberFormatException disebabkan karena terjadi kesalahan dalam format angka.


Sebagai contoh, kita akan mengubah sebuah nilai String menjadi Integer tetapi nilai String
yang akan kita ubah tidak memiliki format angka yang benar, sehingga dapat
membangkitkan NumberFormatException. Berikut contoh kodenya:

1. fun main() {
2.     val someStringValue = "18.0"
3.     println(someStringValue.toInt())
4. }
5.  
6. /*
7. output:
8.     Exception in thread "main" java.lang.NumberFormatException: For input string: "18.0"
9. */

Dan yang terakhir adalah NullPointerException atau NPE. Walaupun Kotlin memiliki


operator Null Safety, NPE tetap bisa saja terjadi. NPE terjadi karena sebuah variabel atau
objek memiliki nilai null, padahal seharusnya objek atau variabel tersebut tidak boleh null.
Berikut contoh kasus yang dapat menyebabkan NullPointerException:

1. fun main() {
2.     val someNullValue: String? = null
3.     val someMustNotNullValue: String = someNullValue!!
4.     println(someMustNotNullValue)
5. }
6.  
7. /*
8. output:
9.     Exception in thread "main" kotlin.NullPointerException at MainKt.main(Main.kt:3)
10. */
Exception handling dapat diterapkan dengan beberapa cara. Di antaranya adalah dengan
menggunakan try-catch, try-catch-finally, dan multiple catch. Mari kita pelajari ketiga
cara tersebut.

try-catch
Salah satu cara untuk menangani suatu exception adalah menggunakan try-catch. Kode
yang dapat membangkitkan suatu exception disimpan dalam blok try, dan
jika exception tersebut terjadi, maka blok catch akan terpanggil. Berikut cara penulisan try-
catch pada Kotlin: 

1. try {
2.     // Block try, menyimpan kode yang membangkitkan exception
3. } catch (e: SomeException) {
4.     // Block catch akan terpanggil ketika exception bangkit.
5. }

Dengan menuliskan kode dalam blok try, kode kita menjadi terproteksi dari exception. Jika
terjadi exception maka program tidak akan terhenti atau crash, namun akan dilempar
menuju blok catch.  Di sana kita dapat menuliskan sebuah kode alternatif untuk
menampilkan pesan eror atau yang lainnya.

1. fun main() {
2.     val someNullValue: String? = null
3.     lateinit var someMustNotNullValue: String
4.  
5.     try {
6.         someMustNotNullValue = someNullValue!!
7.         println(someMustNotNullValue)
8.     } catch (e: Exception) {
9.         someMustNotNullValue = "Nilai String Null"
10.         println(someMustNotNullValue)
11.     }
12. }
13.  
14. /*
15. output:
16.     Nilai String Null
17. */

try-catch-finally
Selain terdapat blok try dan catch, ada juga blok finally. Hanya saja blok ini bersifat
opsional. finally akan dieksekusi setelah program keluar dari blok try ataupun catch.
Bahkan finally juga tereksekusi ketika terjadi exception yang tidak terduga. Exception tidak
terduga terjadi ketika kita
menggunakan NullPointerException pada catch namun exception yang terjadi
adalah NumberFormatException. 

Sebagai contoh, mari kita ubah kode yang sebelumnya dengan menerapkan finally:

1. fun main() {
2.     val someNullValue: String? = null
3.     lateinit var someMustNotNullValue: String
4.  
5.     try {
6.         someMustNotNullValue = someNullValue!!
7.     } catch (e: Exception) {
8.         someMustNotNullValue = "Nilai String Null"
9.     } finally {
10.         println(someMustNotNullValue)
11.     }
12. }
13.  
14. /*
15. output:
16.     Nilai String Null
17. */

Dengan menerapkan finally, fungsi println() cukup dituliskan pada blok finally.

Multiple Catch
Dari kode yang kita coba sebelumnya, kita menggunakan exception untuk menangani
semua tipe exception yang terjadi. Baik itu ketika
terjadi NullPointerException atau NumberFormatException. Sebenarnya pada catch kita
dapat secara spesifik memilih tipe exception apa yang ingin ditangani. Multiple
catch memungkinkan untuk penanganan exception dapat ditangani lebih dari satu
tipe exception. Hal ini sangat berguna ketika kita ingin menangani setiap
tipe exception dengan perlakuan yang berbeda. Berikut contoh struktur kode dengan
menerapkan multiple catch:

1. try{
2.     // Block try, menyimpan kode yang membangkitkan exception
3. } catch (e: NullPointerException) {
4.     // Block catch akan terpanggil ketika terjadi NullPointerException.
5. } catch (e: NumberFormatException) {
6.     // Block catch akan terpanggil ketika terjadi NumberFormatException.
7. } catch (e: Exception) {
8.     // Block catch akan terpanggil ketika terjadi Exception selain keduanya.
9. }
10. finally {
11.     // Block finally akan terpanggil setelah keluar dari block try atau catch
12. }

Dari struktur kode di atas, kita dapat melihat terdapat 3 (tiga) blok catch. Block catch yang
pertama menggunakan parameter NullPointerException, sehingga jika
terjadi NullPointerException maka blok catch tersebut akan dieksekusi. Yang kedua
block catch dengan parameter NumberFormatException, sehingga jika
terjadi NumberFormatException maka blok tersebut yang akan dieksekusi. Dan yang
terakhir blok catch dengan parameter Exception, blok ini akan menangani
seluruh exception yang terjadi kecuali untuk dua exception yang telah ditentukan pada blok
sebelumnya. 

Mari kita coba terapkan contoh kode yang sebelumnya kita buat dengan
menggunakan multiple catch.

1. import kotlin.NumberFormatException
2.  
3. fun main() {
4.     val someStringValue: String? = null
5.     var someIntValue: Int = 0
6.  
7.     try {
8.         someIntValue = someStringValue!!.toInt()
9.     } catch (e: NullPointerException) {
10.         someIntValue = 0
11.     } catch (e: NumberFormatException) {
12.         someIntValue = -1
13.     } finally {
14.         when(someIntValue){
15.             0 -> println("Catch block NullPointerException terpanggil !")
16.             -1 -> println("Catch block NumberFormatException terpanggil !")
17.             else -> println(someIntValue)
18.         }
19.     }
20. }
21.  
22. /*
23. output:
24.     Catch block NullPointerException terpanggil!
25. */

Output kode di atas menjelaskan bahwa blok catch dengan


parameter NullPointerException terpanggil. Sebabnya, pada variabel someStringValue kita
menetapkan null sebagai nilainya.

Berbeda ketika kita menginisialisasi nilai someStringValue dengan nilai “12.0”


maka exception yang akan terjadi adalah NumberFormatException dengan begitu
blok catch kedua yang akan terpanggil dan akan menghasilkan output sebagai berikut:
Catch block NumberFormatException terpanggil!

Namun jika kedua exception tersebut tidak terjadi, dalam arti nilai someStringValue berhasil
diubah dalam bentuk Integer, maka output yang dihasilkan adalah nilai dari Integernya
tersebut. Contohnya, saat nilai someStringValue diinisialisasi dengan nilai “12.” Berikut ini
hasilnya. :

12
Rangkuman dari Kotlin Object-Oriented Programming
Okay, kita sudah selesai belajar dan mengetahui mengenai paradigma Object-Oriented
pada Kotlin. Untuk me-refresh apa yang sudah kita pelajari, mari kita merangkum beberapa
hal penting mengenai paradigma ini.

 Object-Oriented Programming masih menjadi salah satu paradigma atau teknik


pemrograman yang banyak digunakan dalam pengembangan aplikasi.
 Gambaran umum dari Object-Oriented Programming adalah sebuah blueprint, di
mana blueprint ini adalah sebuah Class (kelas).
 Pada Object-Oriented Programming terdapat beberapa komponen penting
yaitu Object, Class, Attribute dan Behaviour.
 Object atau Objek merupakan hasil realisasi dari sebuah blueprint atau class yang
tentunya memiliki fungsi dan juga properti sama seperti blueprint-nya. Salah satu
contoh Object yang sering kita pakai adalah nilai primitif pada Kotlin,
yaitu String, Integer, Char dan Boolean. Kegunaan dari Objek sendiri adalah untuk
mengakses berbagai properti dan fungsi pada kelas.
 Class merupakan sebuah blueprint. Setiap kelas memiliki atribut dan behaviour.
Dalam Kotlin, attributes lebih sering disebut dengan properties,
sedangkan behaviour sering disebut functions. Berikut penjelasan singkat dari setiap
komponen:

o Class: Merupakan sebuah blueprint yang terdapat properti dan fungsi di


dalamnya.
o Properties: Karakteristik dari sebuah kelas, memiliki tipe data.
o Functions: Kemampuan atau aksi dari sebuah kelas.
 Konstruktor merupakan fungsi spesial yang digunakan untuk menginisialisasi properti
yang terdapat pada sebuah kelas.
 Terdapat 3 (tiga) tipe konstruktor pada Kotlin, yaitu primary
constructor, secondary constructor dan default constructor. 

o Primary Constructor

Seperti namanya, jika kita akan membuat suatu objek dari sebuah
kelas dan kelas tersebut memiliki constructor di dalamnya, maka
konstruktor tersebut adalah primary constructor dan diharuskan untuk
mengirim nilai sesuai properti yang dibutuhkan.
 Primary Constructor juga dapat memiliki nilai default, dengan begitu
jika kita tidak menetapkan nilai untuk parameter tersebut maka
properti tersebut akan memiliki nilai default.
 Kotlin menyediakan blok init yang memungkinkan kita untuk
menuliskan properti di dalam body class ketika kita
menggunakan primary constructor. 
o Secondary Constructor

 Secondary constructor digunakan ketika kita ingin menginisialisasi


sebuah kelas dengan cara yang lain. 
 Dapat memiliki lebih dari satu secondary constructor.
o Default Constructor

 Kotlin secara otomatis membuat sebuah default constructor pada


kelas jika kita tidak membuat sebuah konstruktor secara manual.
 Teknik membuat sebuah kelas yang memang bertugas untuk mengatur atau
mengelola fungsi getter dan setter untuk sebuah properti kelas pada Kotlin
dinamakan Delegate.
 Extension properties pada Kotlin sama halnya seperti melakukannya pada
Extension function. Kita dapat menambahkan sebuah properti tanpa harus membuat
sebuah kelas yang mewarisi kelas tersebut. Hal ini dilakukan dengan deklarasi
khusus yang disebut dengan Extension.

 Visibility Modifier adalah hak akses pada Kotlin. Dengan menentukan hak akses


tersebut, kita dapat membatasi akses data pada sebuah kelas. Berikut macam-
macam hak akses dan penjelasan singkatnya yang dapat digunakan pada Kotlin:
o Public: Hak akses yang cakupannya paling luas. Anggota yang
diberi modifier ini dapat diakses dari manapun.
o Private: Hak akses yang cakupannya paling terbatas. Anggota yang
menerapkannya hanya dapat diakses pada scope yang sama.
o Protected: Hak akses yang cakupannya terbatas pada hirarki kelas. Anggota
hanya dapat diakses pada kelas turunannya atau kelas itu sendiri.
o Internal: Hak akses yang cakupannya terbatas pada satu modul. Anggota
yang menggunakannya tidak dapat diakses diluar dari modulnya tersebut.
 Pada Object-Oriented Programming terdapat Inheritance/Pewarisan. Inheritance
dapat mencegah kita melakukan perulangan kode dengan cara mengelompokkan
properti dan fungsi yang sama. Pada Inheritance, terdapat parent class dan
juga child class.
 Pada Kotlin menggunakan dua atau lebih fungsi dengan nama yang sama disebut
dengan overloading. Overloading dapat dilakukan selama fungsi itu memiliki
parameter yan berbeda.
 Abstract Class merupakan gambaran umum dari sebuah kelas. Abstract Class tidak
dapat direalisasikan dalam sebuah objek.
 Interfaces merupakan suatu konsep sifat umum yang nantinya digunakan oleh suatu
kelas agar dapat memiliki sifat tersebut.
 Seluruh konten pada Kotlin, seperti kelas dan fungsi, dibungkus dalam
sebuah package. Package tersebut digunakan untuk mengelompokkan kelas, fungsi
dan variabel yang mempunyai kemiripan fungsionalitas. 
 Exception adalah event (kejadian) yang dapat mengacaukan jalannya suatu
program. Pada Kotlin semua exception bersifat Unchecked, yang
artinya exception terjadi karena kesalahan pada kode kita. Kode yang baik yaitu
kode yang terhindar dari segala bentuk kejadian dengan efek buruk pada aplikasi
kita. Kejadian tersebut pada programming disebut Exception. Hal seperti ini
seharusnya dihindari. Oleh karena itu kita harus mengetahui cara menangani
suatu exception (Exception Handling).
 Berikut ini beberapa contoh Unchecked Exception yang sering mengganggu jalannya
program kita:

o ArithmeticException

 Merupakan exception yang terjadi karena kita membagi suatu


bilangan dengan nilai nol.
o NumberFormatException

 Disebabkan karena terjadi kesalahan dalam format angka.


o NullPointerException

 Terjadi karena sebuah variabel atau objek memiliki nilai null, padahal


seharusnya objek atau variabel tersebut tidak boleh null.
 Exception Handling dapat diterapkan dengan beberapa cara. Di antaranya adalah
dengan menggunakan try-catch, try-catch-finally, dan multiple catch.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari apa dan bagaimana OOP pada Kotlin. Untuk menguji pemahaman
Anda terhadap materi yang sudah dipelajari, Anda perlu mengerjakan beberapa latihan
sederhana. Buka proyek latihan dan kerjakan semua latihan yang ada pada modul Object-
Oriented.
Pada modul sebelumnya kita sudah belajar tentang Kotlin sebagai bahasa pemrograman
yang bisa diklasifikasikan ke dalam OOP beserta konsep-konsep yang terdapat didalamnya.
Kali ini kita akan mempelajari tentang Generics, yaitu sebuah konsep yang memungkinkan
suatu kelas atau interface menjadi tipe parameter yang dapat digunakan untuk berbagai
macam tipe data.

Berkenalan Dengan Generics


Seperti yang kita ketahui, Kotlin termasuk dalam bahasa pemrograman statically
typed. Ketika menambahkan variabel baru, maka secara otomatis tipe dari variabel tersebut
dapat dikenali pada saat kompilasi. 

Secara umum generic merupakan konsep yang digunakan untuk menentukan tipe data yang
akan kita gunakan. Pendeklarasiannya ditandai dengan tipe parameter. Kita juga bisa
mengganti tipe parameter menjadi tipe yang lebih spesifik dengan menentukan instance dari
tipe tersebut.

Sebelum kita mempelajari bagaimana cara kita mendeklarasikan sebuah kelas generic, ada
baiknya jika kita melihat contoh bagaimana generic bekerja pada variabel dengan tipe List.
Kita perlu menentukan tipe dari nilai yang bisa disimpan di dalam variabel List tersebut:

1. val contributor = listOf<String>("jasoet", "alfian","nrohmen","dimas","widy")

Perhatikan kode di atas. Tipe parameter yang digunakan dalam pemanggilan


fungsi listOf() adalah String maka nilai yang bisa kita masukkan adalah nilai dengan tipe
String. Kita bisa menyederhanakannya dengan menghapus tipe parameter tersebut. Karena
kompiler akan menetapkannya secara otomatis bahwa variabel yang kita buat adalah String.

1. val contributor = listOf("alfian","nrohmen","dimas","widy")

Berbeda jika kita ingin membuat variabel list tanpa langsung menambahkan nilainya. Maka
list tersebut tidak memiliki nilai yang bisa dijadikan acuan untuk kompiler menentukan tipe
parameter. Alhasil, kita wajib menentukannya secara eksplisit seperti berikut:

1. val contributor = listOf<String>()

Selain itu, kita juga bisa mendeklarasikan lebih dari satu tipe parameter untuk sebuah kelas.
Contohnya adalah kelas Map yang memiliki dua tipe parameter yang digunakan
sebagai key dan value. Kita bisa menentukannya dengan argumen tertentu, misalnya
seperti berikut:

1. val points = mapOf<String, Int>( "alfian" to 10 , "dimas" to 20 )


Mendeklarasikan Kelas Generic
Setelah mengetahui contoh bagaimana generic bekerja pada sebuah kelas, selanjutnya kita
akan mempelajari bagaimana penerapan generic itu sendiri. Kita bisa menerapkannya
dengan meletakkan tipe parameter ke dalam angle brackets (<>) seperti berikut:

1. interface List<T>{
2.     operator fun get(index: Int) : T
3. }

Pada kode di atas, tipe parameter T bisa kita gunakan sebagai tipe reguler yang
mengembalikan tipe dari sebuah fungsi.

Selanjutnya, jika kita mempunyai sebuah kelas yang mewarisi kelas atau interface generic,
maka kita perlu menentukan tipe argumen sebagai tipe dasar dari parameter generic kelas
tersebut. Parameternya bisa berupa tipe yang spesifik atau lainnya. Contohnya seperti
berikut:

1. class LongList : List<Long>{


2.     override fun get(index: Int): Long {
3.         return this[index]
4.     }
5. }
6.  
7. class ArrayList<T> : List<T>{
8.     override fun get(index: Int): T {
9.       return this[index]
10.     }
11. }

Pada kelas LongList di atas, Long digunakan sebagai tipe argumen untuk List, sehingga
fungsi yang berada di dalamnya akan menggunakan Long sebagai tipe dasarnya. Berbeda
dengan kelas ArrayList, di mana tipe argumen untuk kelas List menggunakan T. Dengan
demikian ketika kita menggunakan kelas ArrayList, kita perlu menentukan tipe argumen dari
kelas tersebut saat diinisialisasi.

1. fun main() {
2.     val longArrayList = ArrayList<Long>()
3.     val firstLong = longArrayList.get(0)
4. }
5.  
6. class ArrayList<T> : List<T> {
7.     override fun get(index: Int): T {
8.         return this[index]
9.     }
10. }
11.  
12. interface List<T> {
13.     operator fun get(index: Int): T
14. }

Yang perlu diperhatikan dari kelas ArrayList di atas adalah deklarasi dari tipe parameter T.
Tipe parameter tersebut berbeda dengan yang ada pada kelas List, karena T adalah milik
kelas ArrayList itu sendiri. Plus sebenarnya Anda pun bisa menggunakan selain T misalnya
seperti berikut:

1. class ArrayList<T> : List<T> {


2.     override fun get(index: Int): T {
3.         return this[index]
4.     }
5. }
6.  
7. interface List<P> {
8.     operator fun get(index: Int): P
9. }
Mendeklarasikan Fungsi Generic
Setelah deklarasi generic pada sebuah kelas, apa berikutnya? Kita akan belajar bagaimana
mendeklarasikan generic pada sebuah fungsi. Generic pada sebuah fungsi dibutuhkan
ketika kita membuat sebuah fungsi yang berhubungan dengan List. Misalnya, list yang dapat
digunakan untuk berbagai tipe dan tidak terpaku pada tipe tertentu. 

Fungsi generic memiliki tipe parameternya sendiri. Tipe argumen dari parameternya
ditentukan ketika fungsi tersebut dipanggil. Cara mendeklarasikannya sedikit berbeda
dengan kelas generic, Tipe parameter yang berada di dalam angle bracket harus
ditempatkan sebelum nama dari fungsi yang kita tentukan. Sebagai contoh:

1. fun <T> run(): T {


2.     /*...*/
3. }

Contoh penerapan fungsi generic bisa kita lihat pada deklarasi fungsi slice yang
merupakan extensions function dari kelas List berikut:

1. public fun <T> List<T>.slice(indices: Iterable<Int>): List<T> {


2.     /*...*/
3. }

Tipe parameter pada fungsi slice() di atas digunakan sebagai receiver dan return type.


Ketika fungsi tersebut dipanggil dari sebuah List dengan tipe tertentu, kita bisa menentukan
tipe argumennya secara spesifik seperti berikut:

1. fun main() {
2.     val numbers = (1..100).toList()
3.     print(numbers.slice<Int>(1..10))
4. }
5.  
6. /*
7.    output : [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
8. */

Seperti yang telah disebutkan sebelumnya, jika semua nilai yang berada di dalamnya
memiliki tipe yang sama, kita bisa menyederhanakan. Caranya, hapus tipe parameter
tersebut.

1. fun main() {
2.     val numbers = (1..100).toList()
3.     print(numbers.slice(1..10))
4. }
5.  
6. /*
7.    output : [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
8. */
Constraint Type Parameter
Dalam penerapan generic, kita bisa membatasi tipe apa saja yang dapat digunakan sebagai
parameter. Untuk menentukkan batasan tersebut, bisa dengan menambahkan tanda titik
dua (:) setelah tipe parameter yang kemudian diikuti oleh tipe yang akan dijadikan batasan.
Contohnya seperti berikut:

1. class ListNumber<T : Number> : List<T>{


2.     override fun get(index: Int): T {
3.         /* .. */
4.     }
5. }

Pada kode di atas kita telah menentukan Number sebagai batasan tipe argumen. Dengan
begitu, kita hanya bisa memasukkan tipe argumen Number pada kelas ListNumber. Dan
ketika kita memasukkan selain Number, maka akan terjadi eror seperti berikut:

1. fun main() {
2.     val numbers = ListNumber<Long>()
3.     val numbers2 = ListNumber<Int>()
4.     val numbers3 = ListNumber<String>() // error : Type argument is not within its
bounds
5. }
6.  
7. class ListNumber<T : Number> : List<T>{
8.     override fun get(index: Int): T {
9.         /* .. */
10.     }
11. }

Contoh lain dari constraint type parameter adalah seperti berikut:

1. fun <T : Number> List<T>.sumNumber() : T {


2.     /* .. */
3. }

Fungsi di atas merupakan extensions function dari kelas List yang mempunyai tipe


parameter. Sama seperti deklarasi generic pada sebuah fungsi, tipe parameter T pada
fungsi tersebut juga akan digunakan sebagai receiver dan return type. Perbedaannya
terletak pada cara memanggilnya. Fungsi tersebut akan tersedia pada variabel List dengan
tipe argumen yang memiliki supertype Number.

1. fun main() {
2.     val numbers = listOf(1, 2, 3, 4, 5)
3.     numbers.sumNumber()
4.     val names = listOf("dicoding", "academy")
5.     names.sumNumber() // error : inferred type String is not a subtype of Number
6. }
7.  
8. fun <T : Number> List<T>.sumNumber() : T {
9.     /* .. */
10. }
Variance
Sebelumnya kita telah mempelajari bagaimana generic bekerja, bagaimana penerapannya,
serta bagaimana kita bisa menentukan batasan tipe argumen yang bisa ditentukan terhadap
tipe parameter. Selanjutnya kita akan belajar salah satu konsep dari generic yaitu variance.

Apa itu variance? Variance adalah konsep yang menggambarkan bagaimana sebuah tipe
yang memiliki subtipe yang sama dan tipe argumen yang berbeda saling berkaitan satu
sama lain. Variance dibutuhkan ketika kita ingin membuat kelas atau fungsi generic dengan
batasan yang tidak akan mengganggu dalam penggunaannya. Sebagai contoh, mari kita
buat beberapa kelas seperti berikut:

1. abstract class Vehicle(wheel: Int)


2. class Car(speed: Int) : Vehicle(4)
3. class MotorCycle(speed: Int) : Vehicle(2)

Kemudian jalankan kode seperti berikut:

1. fun main() {
2.     val car = Car(200)
3.     val motorCycle = MotorCycle(100)
4.     var vehicle: Vehicle = car
5.     vehicle = motorCycle
6. }

Bisa kita perhatikan pada kode di atas, variabel car dan motorcycle merupakan subtipe


dari Vehicle sehingga kita bisa melakukan assignment antar dua variabel tersebut. Maka
seharusnya kode tersebut akan berhasil dikompilasi.

Selanjutnya mari kita masukkan salah satu kelas yang merupakan subtipe dari
kelas Vehicle di atas kedalam generic list:

1. fun main() {
2.     val carList = listOf(Car(100) , Car(120))
3.     val vehicleList = carList
4. }

Dari contoh di atas, kita melihat bagaimana variance menggambarkan keterkaitan


antara carList dan vehicleList di mana Car merupakan subtipe dari Vehicle. 

Nah, itu adalah contoh sederhana bagaimana variance bekerja. Lalu bagaimana cara
membuat kelas generic yang memiliki variance? Caranya sama seperti ketika kita membuat
generic kelas pada umumnya. Namun untuk tipe parameternya kita membutuhkan kata
kunci out untuk covariant atau kunci in untuk contravariant.

Covariant
Contoh deklarasi generic dengan covariant bisa kita lihat saat kelas List pada Kotlin
dideklarasikan seperti berikut:

1. interface List<out E> : Collection<E> {


2.     operator fun get(index: Int): E
3. }

Ketika kita menandai sebuah tipe parameter dengan kata kunci out maka nilai dari tipe
parameter tersebut hanya bisa diproduksi seperti menjadikanya sebagai return type. Serta
tidak dapat dikonsumsi seperti menjadikannya sebagai tipe argumen untuk setiap fungsi di
dalam kelas tersebut. 

Contravariant
Berbanding terbalik dengan saat kita menandainya dengan kata kunci out, bagaimana saat
kita menandainya dengan dengan kata kunci in ?  Nilai dari tipe parameter tersebut bisa
dikonsumsi dengan menjadikannya sebagai tipe argumen untuk setiap fungsi yang ada di
dalam kelas tersebut dan tidak untuk diproduksi. Contoh dari deklarasinya bisa kita lihat
pada kelas Comparable pada Kotlin berikut:

1. interface Comparable<in T> {


2.     operator fun compareTo(other: T): Int
3. }
Rangkuman dari Kotlin Generics
Inti yang telah dipelajari pada modul ini adalah Generics, sebuah konsep yang
memungkinkan suatu kelas atau interface menjadi tipe parameter yang dapat digunakan
untuk berbagai macam tipe data. Tidak hanya itu saja, kita telah mengenal juga
tentang variance, yaitu salah satu konsep dari generics. Berikut adalah rangkuman dari
keseluruhan materi tersebut :

 Generics pada kotlin dapat diterapkan dengan meletakkan tipe parameter ke dalam


angle brackets (<>). Tipe parameter atau alias ini merupakan word yang bisa bebas
Anda deklarasikan, contoh yang paling umum adalah menggunakan word  ‘T’.

1. interface List<T>{
2.     operator fun get(index: Int) : T
3. }

 Ada 2 jenis penerapan generics, yaitu Generics pada Class, dan Generics pada


Function.
 Generics pada Class dilakukan dengan meletakkan tipe parameter setelah
penulisan nama class.

1. interface List<T>{
2.     operator fun get(index: Int) : T
3. }

 Class yang mewarisi sebuah Generic Class atau Interface harus menentukan tipe


argumen sebagai tipe dasar dari parameter generic kelas tersebut. Tipe parameter
yang digunakan bisa berupa tipe yang spesifik (Integer, Float, Double, atau Object
lainnya) atau bisa juga class tersebut mendeklarasikan tipe parameter yang lainnya.
 Generics pada Function dilakukan dengan meletakkan tipe parameter sebelum
penulisan nama dari fungsi tersebut.

1. fun <T> run(): T {
2.     /*...*/
3. }

 Dalam menggunakan sebuah class maupun function generic, tipe parameter


diletakkan setelah memanggil nama dari sebuah function ataupun class.

1. fun main() {
2.     val longArrayList = ArrayList<Long>()
3.     displayArray<Long>(longArrayList)
4. }

 Constraint Type Parameter adalah teknik untuk membatasi cakupan tipe data apa
saja yang dapat di sediakan oleh Generic Class maupun Generic Function.
Pembatasan ini dilakukan dengan membubuhkan tanda colon (:) setelah tipe
parameter yang kemudian diikuti oleh tipe data yang akan dijadikan batasan.
 Batasan dalam Constraint Type Parameter adalah child class dari tipe data
batasan tersebut. Jadi, semisal ditentukan batasannya adalah tipe List, maka hanya
berlaku pada child class dari class tersebut.
 Variance, adalah konsep pada Generics yang menggambarkan bagaimana sebuah
tipe yang memiliki subtipe yang sama dan tipe argumen yang berbeda saling
berkaitan satu sama lain. Ada beberapa jenis dari variance yaitu: 

o Covariant
Ditandai dengan keyword ‘out’ sebelum deklarasi dari tipe parameter. Nilai
dari tipe parameter tersebut hanya bisa diproduksi seperti menjadikanya
sebagai return type dan tidak dapat dikonsumsi seperti menjadikannya
sebagai tipe argumen untuk setiap fungsi di dalam kelas tersebut.
o Contravariant
Ditandai dengan keyword ‘in’ sebelum deklarasi dari tipe parameter. Nilai dari
tipe parameter tersebut hanya bisa dikonsumsi seperti menjadikannya
sebagai tipe argumen untuk setiap fungsi dan tidak untuk dikonsumsi
sebagai return type dari sebuah function di dalam kelas tersebut 
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari bagaimana mendeklarasikan kelas dan fungsi generic pada Kotlin.
Serta mempelajari sebuah konsep yang bernama variance.

Untuk menguji pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu
mengerjakan beberapa latihan sederhana. Buka proyek latihan dan kerjakan semua latihan
yang ada pada modul Generics.
Memasuki modul terakhir ini, kita akan mempelajari dasar concurrency pada Kotlin hingga
alasan mengapa developer wajib mencoba Kotlin Coroutines. Concurrency merupakan
sebuah topik yang cukup dalam. Jika dibahas secara menyeluruh mungkin tidak akan cukup
di akademi ini. Maka dari itu, modul ini adalah pengantarnya. Diharapkan setelah memahami
materi ini, pembaca dapat mengetahui gambaran apa itu concurrency dan perbedaannya pada
Kotlin dibandingkan bahasa pemrograman lainnya.

Apa itu Concurrency?


Concurrency adalah beberapa proses yang terjadi secara bersamaan dalam suatu sistem.
Concurrency merupakan suatu fenomena alami yang umum terjadi. Seperti halnya di dunia
nyata, banyak kegiatan yang dilakukan pada waktu yang bersamaan. Dengan demikian,
ketika kita ingin mengembangkan sebuah sistem untuk membantu kegiatan nyata, tentunya
kita harus peduli dengan yang namanya concurrency.

Arus lalu lintas bisa menjadi ilustrasi yang tepat untuk menggambarkan proses concurrency.
Lalu lintas paralel di jalan yang berbeda hanya akan menimbulkan interaksi dan potensi
masalah yang kecil antar kendaraan. Berbeda dengan lalu lintas padat yang biasanya kita
jumpai pada persimpangan. Pastinya interaksi dan potensi masalah antar kendaraan akan
lebih besar dan membutuhkan koordinasi yang lebih. Begitu pula dalam sebuah sistem
aplikasi. Proses paralel yang tidak saling berinteraksi hanya akan menyebabkan masalah
concurrency yang sederhana. Berbeda dengan proses yang saling berinteraksi bahkan berbagi
sumber daya. Masalahnya tentu lebih kompleks. 

Penting untuk memperhatikan beberapa aspek saat berurusan dengan concurrency pada
semua sistem. Aspek terpenting adalah mampu mendeteksi dan merespon peristiwa eksternal
yang terjadi dalam urutan acak. Selain itu juga pastikan bahwa peristiwa tersebut dapat
ditanggapi dalam interval waktu minimum yang diwajibkan.

Faktor eksternal sering jadi pendorong dibutuhkannya concurrency. Dalam kehidupan sehari-
hari, banyak hal yang terjadi secara bersamaan dan harus ditangani secara real-time oleh
sistem. Oleh karena itu sistem harus "reactive" alias dituntut untuk menanggapi proses yang
dihasilkan secara eksternal. Proses tersebut dapat terjadi pada waktu dan urutan yang tak bisa
ditentukan. Membangun sistem konvensional untuk mengatasi tugas tersebut, tentunya sangat
rumit. 

Di dunia komputer, concurrency umumnya terkait dengan banyaknya core atau inti dari
prosesor (CPU). Pada dasarnya, sebuah komputer memiliki mekanisme sequential atau
berurutan untuk menjalankan tugas. Prosesor akan menjalankan satu perintah pada satu
waktu. Dengan concurrency, kita bisa memanfaatkan kinerja prosesor dengan lebih optimal.
Concurrency memungkinkan sebuah sistem untuk bisa dikendalikan dengan mudah. Sebagai
contoh, suatu fungsi bisa dijalankan, dihentikan, ataupun dipengaruhi oleh fungsi lain yang
jalan secara bersamaan. 
Concurrency vs Parallelism
Jika membahas concurrency, tentunya terkait dengan parallelism. Mungkin ada yang
bingung mengenai perbedaan antara keduanya. Bagaimanapun, concurrency dan
parallelism mempunyai arti yang mirip, yaitu 2 (dua) atau lebih proses yang berjalan pada
satu waktu. Namun penting diketahui bahwa concurrency bukanlah parallelism.

Baik concurrency maupun parallelism, biasanya melibatkan pembuatan thread-thread untuk


menjalankan tugas. Thread-thread tersebut bisa dijalankan di satu atau lebih core. Lalu
apakah perbedaan dari keduanya?

Concurrency terjadi apabila terdapat 2 (dua) atau lebih proses yang tumpang tindih dalam
satu waktu. Ini bisa terjadi jika ada 2 (dua) atau lebih thread yang sedang aktif. Dan jika
thread tersebut dijalankan oleh komputer yang hanya memiliki 1 (satu) core, semua thread
tidak akan dijalankan secara paralel. Concurrency memungkinkan sebuah komputer yang
hanya memiliki 1 (satu) core tampak seakan mengerjakan banyak tugas sekaligus. Padahal
sebenarnya tugas-tugas tersebut dilakukan secara bergantian.

Sedangkan parallelism terjadi ketika 2 (dua) proses dijalankan pada titik waktu yang sama
persis. Parallelism bisa dilakukan jika terdapat 2 (dua) atau lebih thread dan komputer juga
memiliki 2 (dua) core atau lebih. Sehingga setiap core dapat menjalankan perintah dari
masing-masing thread secara bersamaan. 

Perhatikan beberapa ilustrasi berikut agar Anda lebih memahami perbedaan antara
concurrency dan parallelism.

Bayangkan Anda di warung kopi dan melihat antrian seperti yang digambarkan pada
ilustrasi di atas. Pelanggan dengan masing-masing pesanannya pasti senang jika sang
barista bisa melayani dengan tepat dan cepat. Jika pada warung kopi tersebut hanya
terdapat seorang barista, otomatis sang barista harus melakukan cara untuk melayani
semua pelanggan sekaligus. Pada situasi seperti inilah concurrency dibutuhkan.
Karena hanya terdapat seorang barista, maka barista tersebut akan memproses lebih dari
satu pesanan secara bersamaan. Hal ini sangat mungkin terjadi karena pembuatan kopi
membutuhkan beberapa langkah, dan masing-masing langkah memakan waktu tersendiri.
Misalnya menyiapkan air panas, menakar kopi, menyiapkan mesin espresso, dll. Barista
akan membagi langkah-langkah tersebut sehingga seolah-olah ia bisa mengerjakan
pesanan secara bersamaan.

Berbeda jika sang barista punya teman kerja untuk berbagi tugas. Pada situasi ini
parallelism bisa dilakukan. Barista 1 hanya akan melayani beberapa pelanggan, dan sisanya
akan dilayani oleh barista 2.

Karena kedua barista tersebut telah berbagi tugas, maka mereka akan bertindak secara
paralel sehubungan dengan tugas yang lebih besar dalam melayani pelanggan.
Bagaimanapun, selama jumlah barista belum sama dengan jumlah pelanggan, concurrency
masih tetap diperlukan pada masing-masing barista tersebut. Artinya, parallelism dapat
menimbulkan concurrency, tetapi concurrency bisa terjadi tanpa parallelism.
Process, Thread, I/O-Bound
Saat kita mulai menjalankan sebuah aplikasi, sebenarnya sistem operasi akan membuat
sebuah proses, kemudian melampirkan sebuah thread padanya, dan setelah itu mulai
menjalankan thread tersebut. Semua aktivitas tersebut akan bergantung pada perangkat
yang digunakan, terutama perangkat perangkat input dan output (I/O).

Untuk bisa memahami dan juga menerapkan concurrency dengan benar, developer perlu
mempelajari beberapa konsep dasar seperti Process, Thread dan I/O bound. Ketiga
konsep tersebut saling berhubungan. Oleh karena itu, kita akan mencoba mengulas semua
konsep tersebut. 

Process
Sebuah proses (process) merupakan bagian dari aplikasi yang sedang dijalankan. Setiap
kali aplikasi dijalankan, maka saat itu juga proses dijalankan. Tergantung pada sistem
operasi yang digunakan, suatu proses dapat terdiri dari beberapa thread yang menjalankan
instruksi secara bersamaan.

Proses sering dianggap identik dengan program atau aplikasi. Namun, sebenarnya sebuah
aplikasi adalah serangkaian proses yang saling bekerja sama. Untuk memfasilitasi
komunikasi antar proses, sebagian besar sistem operasi mendukung sumber daya Inter
Process Communication (IPC), seperti pipes dan soket. Biasanya sistem operasi modern
sudah mendukung IPC. IPC digunakan tidak hanya untuk komunikasi antar proses pada
sistem yang sama, melainkan juga untuk proses pada sistem yang berbeda.

Kita pasti mengenal dengan sebuah konsep yang bernama multitasking atau melakukan
banyak tugas secara bersamaan. Saat multitasking, sebenarnya sistem operasi hanya
beralih di antara berbagai proses dengan sangat cepat untuk memberikan kesan bahwa
proses ini sedang dijalankan secara bersamaan. Sebaliknya, multiprocessing adalah metode
untuk menggunakan lebih dari satu CPU dalam menjalankan tugas.

Dalam concurrency dan parallelism, multiprocessing mengacu pada pelaksanaan berbagai


proses bersamaan dalam suatu sistem operasi, di mana setiap proses dieksekusi pada CPU
terpisah. Oleh karena itu, multiprocessing merupakan tantangan tersendiri bagi developer
dalam mengembangkan sebuah aplikasi.

Thread
Thread biasa dikenal dengan proses yang ringan. Membuat thread baru membutuhkan lebih
sedikit sumber daya daripada membuat proses baru. Sebuah thread mencakup serangkaian
instruksi untuk dijalankan oleh prosesor. Sehingga suatu proses akan memiliki setidaknya
satu thread, yang dibuat untuk mengeksekusi fungsi utama dari sebuah aplikasi. Secara
umum, thread tersebut disebut dengan main thread, dan life cycle dari sebuah proses akan
terikat padanya.

Lebih dari satu thread dapat diimplementasikan dalam proses yang sama, dan dapat
dieksekusi secara bersamaan. Perbedaan utama antara proses dan thread adalah bahwa
thread biasanya merupakan komponen dari suatu proses. Selain itu, thread biasanya
memungkinkan untuk berbagi sumber daya seperti memori dan data. Dimana 2 (dua) hal
tersebut jarang dilakukan oleh sebuah proses.

Setiap thread dapat mengakses dan memodifikasi sumber daya yang terkandung dalam
proses yang dilampirkan, tetapi juga memiliki penyimpanan lokal sendiri, yang biasa disebut
dengan thread-local storage.

Hanya satu dari instruksi dalam sebuah thread yang dapat dijalankan pada waktu tertentu.
Jadi, jika sebuah thread terblokir, instruksi lain dalam thread yang sama tidak akan
dijalankan sampai pemblokiran tersebut berakhir. Namun demikian, banyak thread dapat
dibuat untuk proses yang sama, dan mereka dapat berkomunikasi satu sama lain. Jadi
diharapkan aplikasi tidak akan pernah memblokir thread yang dapat mempengaruhi
pengalaman pengguna secara negatif.

Selain main thread, terdapat juga thread lain yang dikenal dengan UI thread. Thread ini
berfungsi untuk memperbarui user interface (antarmuka) dan juga merespon aksi yang
diberikan pada aplikasi. Jika thread ini diblokir, maka semua tugasnya akan terhalangi. Oleh
karena itu, jangan sampai kita memblokir UI thread agar aplikasi tetap berjalan dengan
semestinya.

I/O-Bound
Bottlenecks atau kemacetan adalah suatu hal yang penting untuk ditangani demi
mengoptimalkan kinerja aplikasi. Bayangkan saja ketika Anda menggunakan sebuah
aplikasi dan terjadi bottleneck di dalamnya, kesal sendiri kan?
Perangkat input dan output biasanya sering mempengaruhi sebuah aplikasi
mengalami bottlenecks. Sebagai contoh, memori yang terbatas, kecepatan prosesor, dsb.
Lalu bagaimanakah cara untuk mengatasinya?

I/O-bound merupakan sebuah algoritma yang bergantung pada perangkat input atau output.
Waktu untuk mengeksekusi sebuah I/O-bound tergantung pada kecepatan perangkat yang
digunakan. Sebagai contoh, suatu algoritma untuk membaca dan menulis sebuah dokumen.
Ini adalah operasi I/O yang akan tergantung pada kecepatan di mana berkas tersebut dapat
diakses. Berkas yang disimpan pada SSD akan lebih cepat diakses dibandingkan berkas
yang disimpan pada HDD.

Algoritma I/O-bound akan selalu menunggu sesuatu yang lain. Penantian terus-menerus ini
memungkinkan perangkat yang hanya memiliki satu core untuk menggunakan prosesor
demi tugas-tugas bermanfaat lainnya sambil menunggu. Jadi algoritma concurrent yang
terikat dengan I/O akan melakukan hal yang sama, terlepas dari eksekusi yang terjadi -
apakah paralel atau dalam satu core?

Seperti yang telah disebutkan sebelumnya, sangat penting untuk tidak memblokir UI thread
dalam sebuah aplikasi. Oleh karena itu, saran kami terapkanlah concurrency jika aplikasi
yang Anda kembangkan punya ketergantungan dengan perangkat I/O.
Permasalahan Pada Concurrency
Membuat concurrent program ? Banyak developer sering mengeluhkan itu sulit. Selain
kodenya bisa dibilang sulit dituliskan, terdapat juga beberapa tantangan yang perlu dihadapi.
Ada beberapa permasalahan yang wajib bisa kita tangani pada concurrency,
yaitu deadlocks, livelocks, starvation dan juga race conditions.

Deadlocks
Untuk menjamin bahwa kode concurrent bisa disinkronkan dengan benar, apa salah satu hal
yang tidak bisa dihindari ? Kita perlu menunda/memblokir eksekusi saat sebuah perintah
diselesaikan di thread yang berbeda. Hal tersebut disebabkan oleh deadlocks, yaitu sebuah
kondisi di mana dua proses atau lebih saling menunggu proses yang lain untuk
melepaskan resource yang sedang digunakan.

Deadlocks mengakibatkan proses-proses yang sedang berjalan, tak kunjung selesai. Kasus ini
merupakan umum terjadi ketika banyak proses yang saling berbagi sumber daya. Dalam
situasi yang bisa dibilang cukup kompleks itu, tak jarang salah satu proses harus terpaksa
diberhentikan.

Kasus deadlocks sebenarnya bisa kita amati pada kehidupan nyata. Sebagai contoh,
perhatikan ilustrasi berikut ini:
Ilustrasi di atas menggambarkan antrian dua mobil yang sama-sama akan menyeberangi
sebuah jembatan. Jembatan tersebut bisa kita analogikan sebagai sebuah resource yang
dibutuhkan oleh kedua antrian kendaraan. Karena keduanya saling membutuhkan jembatan
dalam waktu yang sama, maka yang terjadi adalah saling menunggu. Alhasil, tak ada
kemajuan pada proses antrian tersebut. Mau tidak mau, salah satu kendaraan harus ada yang
mengalah atau dikalahkan.

Dalam programming situasi seperti itu juga umum terjadi. Misal ada 2 (dua) proses yang
sama-sama menunggu proses satunya selesai, baru proses tersebut bisa selesai. Sama seperti
ilustrasi mobil. Karena keduanya menunggu satu sama lain, tidak ada dari kedua proses
tersebut yang akan selesai. Tugas selanjutnya pun tidak akan pernah bisa dijalankan.
Dalam sebuah sistem jaringan kerap jadi masalah pemicu deadlocks. Terlebih jika problem
tersebut dibarengi race condition. Alhasil, apa yang terjadi? Muncullah kondisi-kondisi tak
terduga yang membuat deadlocks jauh lebih rumit dibandingkan dengan masalah antrian
proses.

Livelocks
Sama halnya dengan deadlocks, livelocks juga merupakan kondisi di mana sebuah proses
tidak bisa melanjutkan tugasnya. Namun yang membedakannya adalah bahwa selama
livelocks terjadi, keadaan dari aplikasi tetap bisa berubah. Walaupun perubahan keadaan
tersebut menyebabkan proses berjalan dengan tidak semestinya.

Pernahkah suatu ketika Anda berjalan di trotoar dan secara tidak langsung berhadapan
dengan orang lain yang berjalan lurus tepat ke arah Anda? Ya, situasi ini pasti sering terjadi
dan kadang bisa menjadi awkward moment. Secara spontan pasti Anda dan orang tersebut
akan berusaha menghindari satu sama lain dengan bergerak ke satu sisi. Tak jarang, Anda
bergerak ke kiri sementara orang di depan Anda bergerak ke kanan. Dan karena kalian berdua
berjalan di arah yang berlawanan, tentunya malah menghalangi jalan masing-masing. Apakah
yang akan terjadi selanjutnya? Bisa jadi, Anda akan bergerak ke kanan, dan pada saat yang
sama orang itu bergerak ke kiri. Sekali lagi kalian tidak dapat melanjutkan perjalanan.
Kejadian tersebut bisa terjadi berulang kali sampai waktu yang tidak diketahui.

Livelock bisa terjadi ketika beberapa proses bisa bereaksi saat mengalami deadlocks. Proses
tersebut mencoba keluar dari situasi deadlock, namun waktu yang tidak tepat,
menghalanginya.
Starvation
Starvation merupakan sebuah kondisi yang biasanya terjadi setelah deadlock. Kondisi
deadlock sering kali menyebabkan sebuah proses kekurangan sumber daya sehingga
mengalami starvation atau kelaparan. Pada kondisi seperti ini, thread tak dapat akses
reguler ke sumber daya bersama dan membuat proses terhenti.

Selain deadlock, hal lain yang bisa mengakibatkan starvation adalah ketika terjadi kesalahan
sistem. Akibatnya, ada ketimpangan dalam pembagian sumber daya. Sebagai contoh, ketika
satu proses bisa mendapat sumber daya, namun tidak dengan proses lain. Biasanya,
kesalahan seperti itu disebabkan oleh algoritma penjadwalan (scheduling algorithm) yang
kurang tepat atau bisa juga karena resource leak atau kebocoran sumber daya.

Salah satu contoh kesalahan algoritma penjadwalan bisa dilihat ketika sebuah sistem
multitasking dirancang dengan tidak baik. Apa akibatnya? Dua tugas pertama selalu beralilh,
sementara yang ketiga tidak pernah berjalan. Tugas ketiga itulah yang bisa dikatakan
menderita starvation.

Salah satu solusi untuk starvation adalah dengan menerapkan algoritma penjadwalan
dengan antrian prioritas (process priority) dan juga menerapkan teknik aging atau
penuaan. Aging merupakan sebuah teknik yang secara bertahap meningkatkan prioritas
sebuah proses yang menunggu dalam sistem pada waktu yang cukup lama.

Race Conditions
Pada pembahasan sebelumnya, kita sudah menyinggung istilah race conditions. Kondisi
seperti apakah itu? Race condition merupakan masalah umum pada concurrency, yaitu
kondisi di mana terdapat banyak thread yang mengakses data yang dibagikan bersama dan
mencoba mengubahnya secara bersamaan. Ini bisa terjadi ketika kode concurrent yang
dituliskan untuk berjalan secara sekuensial. Artinya, sebuah perintah akan selalu dijalankan
dalam urutan tertentu.

Ketika race condition terjadi, maka sistem bisa saja melakukan proses yang tidak
semestinya. Pada saat itu bug akan muncul. Race condition dikenal sebagai masalah yang
sulit untuk direproduksi dan di-debug, karena hasil akhirnya tidak deterministik dan
tergantung pada waktu relatif dari thread yang menghalangi. Masalah yang muncul pada
production bisa jadi tidak ditemukan pada saat debug. Oleh karena itu, lebih baik
menghindari race condition dengan cara berhati-hati saat merencanakan sebuah sistem. Ini
lebih baik daripada harus berusaha memperbaikinya setelah itu. Lebih repot. 

Contoh sederhana dari race condition bisa kita lihat pada aplikasi perbelanjaan online.
Katakanlah Anda menemukan barang yang ingin Anda beli di sebuah toko online. Pada
halaman deskripsi, tampil informasi bahwa stok barang hanya sisa 1 (satu). Lalu Anda
langsung mengambil keputusan dengan menekan tombol beli. Pada saat yang sama, ada
pengguna lain yang ternyata lebih dahulu membelinya. Kondisi seperti inilah yang
mengakibatkan sebuah state atau keadaan, berubah tanpa Anda sadari. Jika sistem tidak
dirancang sedemikian rupa, maka masalah tak terduga, bisa mengemuka.
Coroutines merupakan fitur unggulan pada Kotlin yang diperkenalkan pada Kotlin versi 1.1.
Saat ini coroutines sudah mencapai versi 1.4.2. Coroutines adalah cara baru untuk menulis
kode asynchronous dan non-blocking. Seperti tagline-nya “Don’t block, Keep moving”
yang dikenalkan pada saat rilis Kotlin versi 1.3. [9]

Kenapa coroutines sering disebut sebagai thread yang ringan? Coroutines juga
mendefinisikan eksekusi dari sekumpulan instruksi untuk dieksekusi oleh prosesor. Selain
itu, coroutines juga memiliki life cycle yang sama dengan thread.

Walaupun coroutines dan threads bekerja dengan cara sama, coroutines jauh lebih ringan
dan efisien. Jutaan coroutines dapat berjalan pada beberapa threads. Jika dibandingkan
dengan threads, coroutines tidak hanya mudah diterapkan, melainkan juga jauh
lebih powerful. Kelebihan tersebut terutama berlaku pada lingkungan mobile, di mana
setiap milliseconds kenaikan kinerja sangat diperhitungkan. Selain itu, perbedaan lainnya
adalah coroutines dikelola oleh pengguna, sedangkan threads dikelola oleh sistem operasi.

Coroutines dijalankan di dalam threads. Satu thread dapat memiliki banyak coroutine di
dalamnya. Namun, seperti yang sudah disebutkan, hanya satu instruksi yang dapat
dijalankan dalam thread pada waktu tertentu. Artinya, jika Anda memiliki sepuluh coroutines
di thread yang sama, hanya satu dari sepuluh coroutines tersebut yang akan berjalan pada
titik waktu tertentu.

Walaupun coroutines dijalankan dalam sebuah thread, perlu diperhatikan bahwa keduanya
tak saling terikat. Menariknya, kita juga bisa menjalankan bagian dari coroutine dalam
sebuah thread, menundanya, kemudian melanjutkannya pada thread yang berbeda.
Coroutines cukup fleksibel untuk kita menentukan- Apakah suatu thread akan menjalankan
sebuah coroutine atau justru menahannya?
Memulai Coroutines
Untuk lebih memahami tentang coroutines, mari kita mulai mencobanya langkah demi
langkah. Hal pertama yang wajib Anda tahu adalah bahwa coroutines bukanlah bagian dari
bahasa Kotlin [10]. Coroutines hanyalah library lain yang disediakan oleh JetBrains. Untuk
itu, agar bisa menggunakannya Anda perlu menambahkan dependensi berikut
pada build.gradle.kts:

1. dependencies {
2.     implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.2")
3. }

Dengan menambahkan dependensi di atas, kini Anda sudah siap untuk mencoba
menggunakan fitur-fitur coroutines dalam membuat program concurrency. Yuk kita mulai
dari kode yang sangat sederhana berikut ini:

1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking{
4.     launch {
5.         delay(1000L)
6.         println("Coroutines!")
7.     }
8.     println("Hello,")
9.     delay(2000L)
10. }

Kode di atas menggambarkan bagaimana coroutines bekerja. Kita menggunakan


fungsi runBlocking untuk memulai coroutine utama dan launch untuk menjalankan coroutine
baru. Jika Anda menjalankan program tersebut, maka konsol akan menampilkan hasil ini:
Hello,
Coroutines!

Kata Hello, akan ditampilkan lebih awal dan kata Coroutines! Akan ditampilkan 1 detik


setelahnya. Mengapa demikian? Padahal jika diperhatikan, kode untuk menampilkan
kata Coroutines! dituliskan lebih dulu.

Fungsi delay(1000L) di dalam launch digunakan untuk menunda kode berikutnya selama 1


detik. delay adalah fungsi yang spesial pada coroutines. Ia merupakan sebuah suspending
function yang tidak akan memblokir sebuah thread.

Selama proses penundaan tersebut, main thread akan terus berjalan sehingga
fungsi println("Hello,") akan langsung dijalankan. Setelah 1 detik, baru
fungsi println("Coroutines!") akan dijalankan. Sedangkan kode delay(2000L) digunakan
untuk menunda selama 2 detik sebelum JVM berakhir. 

Ini baru sekedar permulaan loh. Masih banyak lagi fungsi-fungsi menarik lain pada
coroutines yang dapat mempermudah kita dalam membuat program concurrency. Anda bisa
memanfaatkan kumpulan library yang dapat ditemukan pada repositori kotlinx.coroutines.
JetBrains selaku tim pengembang juga berkomitmen untuk mempertahankan backward
compatibility untuk tiap perubahan yang akan dirilis. Itulah mengapa coroutines sudah
diperkenalkan pada Kotlin versi 1.1.

Tersedia juga panduan resmi untuk langkah-langkah penerapan coroutines. Ikuti saja tautan
ini. 
Coroutines Builder
Pada modul sebelumnya kita sudah mencoba menggunakan
fungsi runBlocking dan launch untuk memulai sebuah coroutines. Kedua fungsi tersebut
merupakan coroutines builder, yaitu sebuah fungsi yang mengambil suspending lambda dan
membuat coroutine untuk menjalankannya.

Kotlin menyediakan beberapa coroutine builder yang bisa disesuaikan dengan berbagai
macam skenario, seperti:

 launch
Seperti yang sudah kita coba sebelumnya, fungsi ini digunakan untuk memulai
sebuah coroutines yang tidak akan mengembalikan sebuah hasil. launch akan
menghasilkan Job yang bisa kita gunakan untuk membatalkan eksekusi.
 runBlocking
Fungsi ini dibuat untuk menjembatani blocking code menjadi kode yang dapat
ditangguhkan. runBlocking akan memblokir sebuah thread yang sedang berjalan
hingga eksekusi coroutine selesai. Seperti contoh sebelumnya, kita bisa
menggunakannya pada fungsi main() dan bisa juga untuk penerapan unit test. 
 async
Kebalikan dari launch, fungsi ini digunakan untuk memulai sebuah coroutine yang
akan mengembalikan sebuah hasil. Ketika menggunakannya, Anda harus berhati-
hati karena ia akan menangkap setiap exception yang terjadi di dalam coroutine.
Jadi async akan mengembalikan Deferred yang berisi hasil atau exception. Ketika
yang dikembalikan adalah exception, maka Anda harus siap untuk menanganinya.
Sekarang giliran kita untuk mencoba contoh penerapan coroutine dengan async. Bayangkan
jika kita memiliki 2 (dua) suspending function seperti berikut:

1. suspend fun getCapital(): Int {


2.     delay(1000L)
3.     return 50000
4. }
5.  
6. suspend fun getIncome(): Int {
7.     delay(1000L)
8.     return 75000
9. }

Anggap saja bahwa delay pada kedua fungsi tersebut adalah waktu yang dibutuhkan untuk
melakukan operasi sebelum hasilnya didapatkan. Selanjutnya kita ingin memanfaatkan
keduanya, misalnya untuk menghitung keuntungan seperti berikut:

1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking {
4.     val capital = getCapital()
5.     val income = getIncome()
6.     println("Your profit is ${income - capital}")
7. }

Pada kode di atas, kita menggunakan pendekatan sequential. Kenapa? Pada dasarnya


kode di dalam coroutines juga dijalankan secara berurutan seperti kode normal lain. Dalam
praktiknya kita melakukan ini jika kita menggunakan hasil dari fungsi pertama untuk
membuat keputusan apakah kita perlu memanggil fungsi kedua.

Bagaimana jika tidak ada ketergantungan antara fungsi getCapital dan getIncome dan kita


ingin menjalankan keduanya secara bersamaan? Di sinilah async dibutuhkan. Kita bisa
menuliskan kode seperti berikut:
1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking {
4.     val capital = async { getCapital() }
5.     val income = async { getIncome() }
6.     println("Your profit is ${income.await() - capital.await()}")
7. }

Dengan kode tersebut, kita telah memanggil fungsi getCapital dan getIncome di


dalam async.Maka async akan mengembalikan hasil dari masing-masing fungsi. Lalu untuk
mengakses hasil tersebut, kita perlu menggunakan fungsi await.

Wait.. adakah perbedaan dengan kode sebelumnya? Dengan async seolah-olah kedua


fungsi tersebut berjalan bersamaan dan membutuhkan waktu yang lebih singkat dari kode
sebelumnya. Untuk membuktikannya, yuk coba jalankan kode berikut:

1. import kotlinx.coroutines.*
2. import kotlin.system.measureTimeMillis
3.  
4. fun main() = runBlocking {
5.     val timeOne = measureTimeMillis {
6.         val capital = getCapital()
7.         val income = getIncome()
8.         println("Your profit is ${income - capital}")
9.     }
10.  
11.     val timeTwo = measureTimeMillis {
12.         val capital = async { getCapital() }
13.         val income = async { getIncome() }
14.         println("Your profit is ${income.await() - capital.await()}")
15.     }
16.  
17.     println("Completed in $timeOne ms vs $timeTwo ms")
18.  
19. }

Konsol akan menampilkan hasil berikut:


Your profit is 25000
Your profit is 25000
Completed in 2013 ms vs 1025 ms

Kita bisa lihat bahwa kode yang dijalankan di dalam async bisa selesai hampir 2 kali lebih
cepat dibandingkan tanpa async!
Job dan Deferred
Secara umum, fungsi asynchronous pada coroutines terbagi menjadi 2 (dua) jenis, yaitu
fungsi yang mengembalikan hasil dan sebaliknya, fungsi yang tidak mengembalikan hasil.
Fungsi yang mengembalikan hasil biasanya digunakan jika kita menginginkan sebuah data
ketika fungsi tersebut selesai dijalankan. Sebagai contoh, fungsi untuk mengambil informasi
dari web service yang menghasilkan respon berupa JSON atau yang lainnya. Sedangkan
fungsi yang tidak mengembalikan hasil biasanya digunakan untuk mengirimkan analitik,
menuliskan log, atau tugas sejenis lainnya.

Sebagai developer, tentunya kita menginginkan tetap bisa mengakses fungsi yang sudah
dijalankan. Misalnya, ketika kita ingin membatalkan tugasnya atau memberikan instruksi
tambahan ketika fungsi tersebut telah mencapai kondisi tertentu. Untuk bisa melakukannya,
Anda perlu memahami tentang Job dan Deferred pada coroutines.

Job
Job adalah sebuah hasil dari perintah asynchronous yang dijalankan. Objek dari job akan
merepresentasikan coroutine yang sebenarnya. Sebuah job akan memiliki 3 (tiga) properti
yang nantinya bisa dipetakan ke dalam setiap state atau keadaan. Berikut adalah ketiga
properti tersebut:

1. isActive
Sebuah properti yang menunjukkan ketika sebuah job sedang aktif.
2. isCompleted
Sebuah properti yang menunjukkan ketika sebuah job telah selesai.
3. isCancelled
Sebuah properti yang menunjukkan ketika sebuah job telah dibatalkan.
Pada dasarnya, job akan segera dijalankan setelah ia dibuat. Namun kita juga bisa membuat
sebuah job tanpa menjalankannya. Job memiliki beberapa siklus hidup mulai dari pertama
kali ia dibuat hingga akhirnya selesai. Kira-kira seperti inilah siklus dari sebuah job jika
digambarkan dalam sebuah diagram:
Dari diagram di atas, kita bisa melihat bahwa job akan melewati beberapa state. Pada setiap
state tersebut nantinya kita bisa memberikan instruksi sesuai yang kita inginkan. Sebelum
kita mengolahnya, mari pahami terlebih dahulu semua state yang ada pada sebuah job.

 New
Keadaan di mana sebuah job telah diinisialisasi namun belum pernah dijalankan.
 Active
Sebuah job akan memiliki status aktif ketika ia sedang berjalan. Dalam hal ini, job
yang sedang ditangguhkan (suspended job) juga termasuk ke dalam job yang aktif.
 Completed
Ketika job sudah tidak berjalan lagi. Ini berlaku untuk job yang berakhir secara
normal, dibatalkan, ataupun karena suatu pengecualian.
 Cancelling
Suatu kondisi ketika fungsi cancel() dipanggil pada job yang sedang aktif dan
memerlukan waktu untuk pembatalan tersebut selesai.
 Cancelled
Keadaan yang dimiliki oleh sebuah job yang sudah berhasil dibatalkan. Perlu
diketahui bahwa job yang dibatalkan juga dapat dianggap sebagai Completed job.
Membuat Job Baru
Job dapat diinisialisasikan menggunakan fungsi launch() maupun Job() seperti berikut:

1. //menggunakan launch():
2. fun main() = runBlocking {
3.     val job = launch {
4.         // Do background task here
5.     }
6. }
7.  
8. //menggunakan Job():
9. fun main() = runBlocking {
10.     val job = Job()
11. }

Setelah diinisialisasikan, job akan memiliki state New dan akan langsung dijalankan. Jika


Anda ingin membuat sebuah job tanpa langsung menjalankannya, Anda bisa
memanfaatkan CoroutineStart.LAZY seperti berikut:

1. fun main() = runBlocking {


2.     val job = launch(start = CoroutineStart.LAZY) {
3.         TODO("Not implemented yet!")
4.     }
5. }

Dengan begitu job tersebut bisa dijalankan saat nanti dibutuhkan.

Menjalankan Job
Setelah membuat sebuah job, kini kita bisa mulai menjalankan job tersebut. Caranya pun
cukup sederhana, kita bisa menggunakan fungsi start() seperti berikut:

1. fun main() = runBlocking {


2.     val job = launch(start = CoroutineStart.LAZY) {
3.         delay(1000L)
4.         println("Start new job!")
5.     }
6.  
7.     job.start()
8.     println("Other task")
9. }

Atau bisa juga dengan menggunakan fungsi join():

1. fun main() = runBlocking {


2.     val job = launch(start = CoroutineStart.LAZY) {
3.         delay(1000L)
4.         println("Start new job!")
5.     }
6.  
7.     job.join()
8.     println("Other task")
9. }

Perbedaan dari keduanya adalah bahwa yang start() akan memulai job tanpa harus
menunggu job tersebut selesai, sedangkan join() akan menunda eksekusi sampai job
selesai. Jika kode pertama dijalankan, maka konsol akan menampilkan hasil berikut:

Other task
Start new job!
Sedangkan kode kedua akan menampilkan hasil:

Start new job!

Other task
Setelah dijalankan,  job akan memiliki state Active.
Membatalkan Job
Ibarat pekerjaan di dunia nyata, sebuah job seharusnya bisa dibatalkan. Hanya job yang
sedang aktif yang dapat dibatalkan. Anda bisa melakukannya dengan memanggil
fungsi cancel() seperti berikut:

1. fun main() = runBlocking {


2.     val job = launch {
3.         delay(5000)
4.         println("Start new job!")
5.     }
6.  
7.     delay(2000)
8.     job.cancel()
9.     println("Cancelling job...")
10.     if (job.isCancelled){
11.         println("Job is cancelled")
12.     }
13. }

Kode di atas menggambarkan sebuah job membutuhkan waktu 5 detik untuk dijalankan.
Namun ketika mencapai waktu 2 detik, job tersebut telah dibatalkan. Saat
fungsi cancel() dipanggil, job akan memasuki state Cancelling sampai pembatalan tersebut
berhasil. Kemudian setelah pembatalan berhasil, job akan
memiliki state Cancelled dan Completed.

Perlu diketahui bahwa jika cancel() dipanggil dalam job baru yang belum dijalankan, job
tersebut tidak akan melalui state Cancelling, melainkan akan langsung
memasuki state Cancelled.

Kita juga bisa menambahkan parameter terhadap fungsi cancel(), yaitu


parameter cause yang bisa digunakan untuk memberitahu kenapa sebuah job dibatalkan.

1. job.cancel(cause = CancellationException("Time is up!"))

CancellationException akan mengirimkan nilainya sebagai pengecualian dari job tersebut.


Kita pun bisa mengakses nilai tersebut dengan fungsi getCancellationException.
Karena getCancellationException masih tahap eksperimen, Anda perlu menambahkan
anotasi @InternalCoroutinesApi. Cobalah modifikasi dan jalankan kode Anda:

1. @InternalCoroutinesApi
2. fun main() = runBlocking {
3.     val job = launch {
4.         delay(5000)
5.         println("Start new job!")
6.     }
7.  
8.     delay(2000)
9.     job.cancel(cause = CancellationException("time is up!"))
10.     println("Cancelling job...")
11.     if (job.isCancelled){
12.         println("Job is cancelled because ${job.getCancellationException().message}")
13.     }
14. }

Konsol akan menampilkan hasil berikut:

Cancelling job...
Job is cancelled because time is up!
Deferred
Seperti yang sudah disampaikan sebelumnya di bagian coroutines builder,
fungsi async akan mengembalikan nilai deferred yang berupa hasil atau exception. Deferred
adalah nilai tangguhan yang dihasilkan dari proses coroutines. Nilai ini nantinya bisa kita
kelola sesuai dengan kebutuhan. 

Deferred dapat kita ciptakan secara manual. Meskipun begitu, dalam praktiknya, jarang kita
membuat deferred secara manual. Biasanya kita hanya bekerja dengan deferred yang
dihasilkan oleh async.

Deferred juga memiliki life cycle yang sama dengan job. Perbedaannya hanyalah pada tipe
hasil yang diberikan. Selain memberikan hasil ketika proses komputasi sukses, ia juga bisa
memberikan hasil saat proses tersebut gagal. Hasil dari deferred tersedia ketika
mencapai state completed dan dapat diakses dengan fungsi await. Deferred akan
mengirimkan pengecualian jika ia telah gagal. Kita bisa mengakses nilai pengecualian
tersebut dengan fungsi getCompletionExceptionOrNull.

Pada dasarnya, nilai deferred juga merupakan sebuah job. Ia diciptakan dan dimulai pada
saat coroutines mencapai state active. Bagaimanapun, fungsi async juga memiliki opsional
parameter seperti CoroutineStart.LAZY untuk memulainya. Dengan begitu, deferred juga
bisa diaktifkan saat fungsi start, join, atau await dipanggil.

Di modul sebelumnya kita sudah membahas kode berikut ini:

1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking {
4.     val capital = async { getCapital() }
5.     val income = async { getIncome() }
6.     println("Your profit is ${income.await() - capital.await()}")
7. }

capital dan income adalah contoh dari nilai deferred yang untuk mengaksesnya kita


membutuhkan fungsi await. 
Coroutine Dispatcher
Seperti yang sudah kita ketahui, coroutines berjalan di atas sebuah thread. Tentunya kita
harus mengetahui thread mana yang akan digunakan untuk menjalankan dan melanjutkan
sebuah coroutine. Untuk menentukannya kita membutuhkan sebuah base
class bernama CoroutineDispatcher. Di dalam kelas tersebut kita akan menemukan
beberapa objek yang nantinya bisa digunakan untuk menentukan thread yang berfungsi
menjalankan coroutines.

 Dispatcher.Default

Merupakan dispatcher dasar yang digunakan oleh semua standard


builders seperti launch, async, dll jika tidak ada dispatcher lain yang
ditentukan. Dispatcher.Default menggunakan kumpulan thread yang ada pada JVM.
Pada dasarnya, jumlah maksimal thread yang digunakan adalah sama dengan
jumlah core dari CPU.
Untuk menggunakannya, Anda cukup menggunakan coroutines builder tanpa harus
menuliskan dispatcher secara spesifik:

1. launch {
2.     // TODO: Implement suspending lambda here
3. }

Namun Anda juga tetap diperbolehkan untuk menuliskannya secara eksplisit:

4. launch(Dispatcher.Default){
5.     // TODO: Implement suspending lambda here
6. }
 Dispatcher.IO
Sebuah dispatcher yang dapat digunakan untuk membongkar pemblokiran operasi
I/O. Ia akan menggunakan kumpulan thread yang dibuat berdasarkan permintaan.
Anda bisa menerapkannya dengan menambahkan Dispatcher.IO pada coroutines
builder:
1. launch(Dispatcher.IO){
2.     // TODO: Implement algorithm here
3. }
 Dispatcher.Unconfined
Dispatcher ini akan menjalankan coroutines pada thread yang sedang berjalan
sampai mencapai titik penangguhan. Setelah penangguhan, coroutines akan
dilanjutkan pada thread dimana komputasi penangguhan yang dipanggil.
Sebagai contoh, ketika fungsi a memanggil fungsi b, yang dijalankan dengan
dispatcher dalam thread tertentu, fungsi a akan dilanjutkan dalam thread yang sama
dengan fungsi b dijalankan. Perhatikan kode berikut:
1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking<Unit> {
4.     launch(Dispatchers.Unconfined) {
5.         println("Starting in ${Thread.currentThread().name}")
6.         delay(1000)
7.         println("Resuming in ${Thread.currentThread().name}")
8.     }.start()
9. }

Jika dijalankan maka konsol akan menampilkan hasil berikut:

Starting in main
Resuming in kotlinx.coroutines.DefaultExecutor

Artinya, coroutine telah dimulai dari main thread, kemudian tertunda oleh fungsi
delay selama 1 detik. Setelah itu, coroutine dilanjutkan kembali pada
thread DefaultExecutor.

Bersamaan dengan objek-objek tersebut, ada beberapa builder yang dapat digunakan untuk
menentukan thread yang dibutuhkan:

 Single Thread Context


Dispatcher ini menjamin bahwa setiap saat coroutine akan dijalankan pada thread
yang Anda tentukan. Untuk menerapkannya, Anda bisa
memanfaatkan newSingleThreadContext()seperti kode dibawah ini:
1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking<Unit> {
4.     val dispatcher = newSingleThreadContext("myThread")
5.     launch(dispatcher) {
6.         println("Starting in ${Thread.currentThread().name}")
7.         delay(1000)
8.         println("Resuming in ${Thread.currentThread().name}")
9.     }.start()
10. }

Jalankan kode tersebut, seharusnya konsol akan menampilkan hasil berikut:

Starting in myThread
Resuming in myThread

Walaupun sudah menjalankan fungsi delay, coroutine akan tetap berjalan


pada myThread.

 Thread Pool
Sebuah dispatcher yang memiliki kumpulan thread. Ia akan memulai dan
melanjutkan coroutine di salah satu thread yang tersedia pada kumpulan tersebut.
Runtime akan menentukan thread mana yang tersedia dan juga menentukan
bagaimana proses distribusi bebannya.
Anda bisa menerapkan thread pool dengan
fungsi newFixedThreadPoolContext() seperti berikut:
1. import kotlinx.coroutines.*
2.  
3. fun main() = runBlocking<Unit> {
4.     val dispatcher = newFixedThreadPoolContext(3, "myPool")
5.  
6.     launch(dispatcher) {
7.         println("Starting in ${Thread.currentThread().name}")
8.         delay(1000)
9.         println("Resuming in ${Thread.currentThread().name}")
10.     }.start()
11. }
Pada kode di atas, kita telah menetapkan thread myPool sebanyak 3 thread.
Runtime akan secara otomatis menentukan pada thread mana coroutine akan
dijalankan dan dilanjutkan. Hasil dari kode tersebut adalah:

Starting in myPool-1
Resuming in myPool-2
Channels
Kita sudah belajar bagaimana membuat dan mengelola coroutines. Seperti kita ketahui,
sebuah program dapat memiliki banyak thread dan dalam beberapa thread bisa terdapat
jutaan coroutines. Lalu, bagaimana jika ada 2 (dua) coroutines yang saling ingin berinteraksi
satu sama lain? Channels adalah jawabnya.

Beberapa masalah yang muncul pada concurrency seperti deadlock, race conditions, dan
lainnya, sering kali dipicu oleh satu hal, apa itu? Rupanya problem pembagian memori atau
sumber daya antar thread. Untuk mengatasinya, banyak programming
language seperti Go, Dart, dan juga Kotlin telah menyediakan channels.

Channels adalah nilai deferred yang menyediakan cara mudah untuk mentransfer nilai
tunggal antara coroutine. Pada dasarnya, channels sangat mirip dengan
BlockingQueue [11]. Namun, alih-alih memblokir sebuah thread, channels menangguhkan
sebuah coroutine yang jauh lebih ringan. Untuk lebih memahaminya, mari simak kode di
bawah ini:

1. import kotlinx.coroutines.*
2. import kotlinx.coroutines.channels.Channel
3.  
4. fun main() = runBlocking(CoroutineName("main")) {
5.     val channel = Channel<Int>()
6.     launch(CoroutineName("v1coroutine")){
7.         println("Sending from ${Thread.currentThread().name}")
8.         for (x in 1..5) channel.send(x * x)
9.     }
10.  
11.     repeat(5) { println(channel.receive()) }
12.     println("received in ${Thread.currentThread().name}")
13. }

Kode di atas akan menghasilkan hasil berikut:

Sending from main @v1coroutine#2


1
4
9
16
25
received in main @main#1
Bisa dilihat bahwa pada coroutine v1coroutine bahwa channels telah mengirimkan nilai dari
hasil komputasi dengan menggunakan fungsi send. Setelah itu, di coroutine lain (main)
channel menerima nilai dengan menggunakan fungsi receive.

Kesimpulannya, channels memungkinkan komunikasi yang aman antar kode concurrent. Ia


membuat kode concurrent dapat berkomunikasi dengan mengirim dan menerima pesan
tanpa harus peduli di thread mana coroutine berjalan. [12]

Selengkapnya tentang channel Anda bisa mempelajarinya pada tautan ini.


Rangkuman dari Berkenalan dengan Coroutines
Di modul ini, kita telah membahas tentang apa itu concurrency, pararelism, threading,
bahkan penerapannya pada kotlin dengan menggunakan fitur Coroutines. Cukup menarik
dan mudah ‘kan ? Oke, mari kita ingat - ingat kembali apa saja yang telah dipelajari hingga
saat ini:

 Concurrency adalah beberapa proses yang terjadi pada suatu sistem. Terjadi


apabila terdapat 2 (dua) atau lebih proses yang tumpang tindih dalam satu waktu.
 Parallelism sama seperti concurrency, namun 2 (dua) atau lebih proses tersebut
dijalankan pada waktu yang sama persis.
 Ada beberapa konsep dasar dari penerapan concurrency yang harus dipelajari yaitu:

o Process
Merupakan bagian dari proses aplikasi yang dijalankan. Setiap kali aplikasi
dijalankan, maka saat itu juga proses dijalankan. Tergantung pada sistem
operasi yang digunakan, suatu proses dapat terdiri dari beberapa thread yang
menjalankan instruksi secara bersamaan.
o Thread
Biasa dikenal sebagai proses yang ringan dan merupakan komponen dari
suatu proses aplikasi yang menjalankan tugas tertentu secara spesifik.
o I/O-Bound
Sebuah algoritma yang bergantung pada perangkat input atau output. Waktu
untuk mengeksekusi sebuah I/O-bound tergantung pada kecepatan
perangkat yang digunakan.
 Ada beberapa permasalahan yang ditimbulkan ketika menerapkan concurrency pada
aplikasi, yaitu :

o Deadlocks
Sebuah kondisi di mana dua proses atau lebih saling menunggu proses yang
lain untuk melepaskan resource yang sedang digunakan yang
mengakibatkan proses yang sedang berjalan tak kunjung selesai melakukan
operasinya.
o Livelocks
Kondisi di mana sebuah proses tidak bisa melanjutkan tugasnya, sama
seperti deadlocks. Namun, perbedaannya adalah selama livelocks terjadi,
keadaan aplikasi tetap bisa berubah. Walau perubahan keadaan tersebut
menyebabkan proses berjalan dengan tidak semestinya.
o Starvation
Kondisi yang biasa terjadi setelah deadlock. Kondisi ini sering kali
menyebabkan sebuah proses kekurangan sumber daya. Pada kondisi ini,
thread menjadi tidak mendapatkan akses reguler ke sumber daya bersama
dan membuat proses terhenti.
o Race Conditions
Kondisi di mana terdapat banyak thread yang mengakses data yang
dibagikan bersama dan mencoba mengubahnya secara bersamaan. Ini bisa
terjadi ketika kode concurrent yang dituliskan untuk berjalan secara
sekuensial.
 Coroutines merupakan fitur unggulan dan cara baru dalam menuliskan thread yang
ringan dan efisien pada Kotlin. Disediakan JetBrains khusus untuk Kotlin.
 Untuk menjalankan coroutine, diperlukan fungsi yang biasa disebut
sebagai coroutine builder yang mengambil suspending lambda dan membuat
coroutine untuk menjalankannya. Ada beberapa macam coroutine builder yaitu:
o launch
Fungsi ini digunakan untuk memulai sebuah coroutines yang tidak akan
mengembalikan sebuah hasil dan menghasilkan Job yang dapat digunakan
untuk membatalkan eksekusi.
o runBlocking
Dibuat untuk menjembatani blocking code menjadi kode yang dapat
ditangguhkan. runBlocking akan memblokir sebuah thread yang sedang
berjalan hingga eksekusi coroutine selesai
o async
Fungsi ini digunakan untuk memulai sebuah coroutine yang akan
mengembalikan sebuah hasil.
 Job merupakan hasil dari perintah asynchronous yang dijalankan dan
merepresentasikan coroutine yang sebenarnya. Memiliki 3 properti terdiri
dari isActive, isCompleted, dan isCancelled yang menunjukkan state dari Job
tersebut. 
 Deferred adalah nilai kembalian dari fungsi async yang dapat berupa hasil atau
exception. Merupakan nilai tangguhan yang dihasilkan dari proses coroutines dan
bisa kita kelola sesuai dengan kebutuhan.
 CoroutineDispatcher, merupakan base class yang menentukan thread yang
berfungsi untuk menjalankan coroutines. Class ini diimplementasikan pada beberapa
class berikut :

o Dispatcher.Default
Merupakan dispatcher dasar yang digunakan oleh semua standard builders
seperti launch, async, dll jika tidak ada dispatcher lain yang ditentukan.
Dispatcher.Default menggunakan kumpulan thread yang ada pada JVM.
Jumlah maksimal thread yang digunakan adalah sama dengan jumlah core
dari CPU. 
o Dispatcher.IO
Merupakan dispatcher yang digunakan untuk membongkar pemblokiran
operasi I/O dan akan menggunakan kumpulan thread yang dibuat
berdasarkan permintaan.
o Dispatcher.Unconfined
Merupakan Dispatcher yang menjalankan coroutines pada thread yang
sedang berjalan sampai mencapai titik penangguhan. Setelah penangguhan,
coroutines akan dilanjutkan pada thread dimana komputasi penangguhan
yang dipanggil.
 Selain dengan menggunakan metode diatas, ada beberapa builder lain yang dapat
digunakan untuk menentukan thread yang dibutuhkan, antara lain:

o Single Thread Context


Dispatcher ini menjamin bahwa setiap saat coroutine akan dijalankan pada
thread yang ditentukan. Penerapannya dilakukan dengan memanfaatkan
fungsi newSingleThreadContext() pada parameter dispatcher dalam
fungsi launch().
o Thread Pool
Sebuah dispatcher yang memiliki kumpulan thread. Ia akan memulai dan
melanjutkan coroutine di salah satu thread yang tersedia pada kumpulan
tersebut. Runtime akan menentukan thread mana yang tersedia dan juga
menentukan bagaimana proses distribusi bebannya. Penerapannya dilakukan
dengan memanfaatkan fungsi newFixedThreadPoolContext() pada
parameter dispatcher dalam fungsi launch().
 Channel adalah adalah nilai deferred yang menyediakan cara mudah untuk
mentransfer nilai tunggal antara coroutine atau bisa disebut melakukan interaksi
antar coroutine serta membuat kode concurrent dapat berkomunikasi dengan
mengirim dan menerima pesan tanpa harus peduli di thread mana coroutine berjalan.
Persiapan
Setiap modul pada akademi ini memiliki beberapa latihan yang harus Anda kerjakan. Latihan-latihan yang ada akan
meningkatkan pemahaman Anda terhadap sebuah materi, jadi sangat disarankan untuk Anda menyelesaikannya
segera setelah membaca 1 (satu) modul besar. Untuk mengerjakan latihan, Anda akan menggunakan sebuah
plugin pada IntelliJ IDEA, yaitu EduTools. Dengan EduTools, semua pekerjaan Anda akan diperiksa secara otomatis.

Sebelum mengerjakan soal latihan, pastikan terlebih dahulu jika soal latihan tersebut adalah soal yang up-
to-date. Caranya, cukup dengan melakukan Synchronize Course pada IntelliJ IDEA. Ini bertujuan agar
peserta mudah dalam menyelesaikan tugas submission di akhir pembelajaran.

Latihan
Anda sudah mempelajari apa itu concurrency dan juga Kotlin coroutines yang
memungkinkan Anda untuk membuat concurrent program dengan sangat mudah.

Untuk menguji pemahaman Anda terhadap materi yang sudah dipelajari, Anda perlu
mengerjakan beberapa latihan sederhana. Buka proyek latihan dan kerjakan semua latihan
yang ada pada modul Coroutines.
Rangkuman Kelas Memulai
Pemrograman dengan Kotlin

Rangkuman dari Pendahuluan


Kotlin merupakan bahasa pemrograman open-source yang mudah dipelajari oleh siapapun.
Ini bisa dilakukan berkat kejeniusan para developer yang dipimpin langsung oleh Andrey
Breslav. Selain mudah dipelajari, Kotlin memungkinkan kita untuk membuat program antar
platform yang tentunya ini dapat mengurangi biaya dalam pembuatan program itu sendiri. 

Selain itu dalam sub-modul pendahuluan ini Anda juga telah mengetahui beberapa hal
sebagai berikut:

1. Selain mudah dipelajari, Anda juga dapat ikut berkontribusi di dalamnya karena
Kotlin merupakan project open-source.
2. Kotlin mendukung 2 (dua) paradigma umum yang akan Anda sering jumpai
yaitu object-oriented programming (OOP) dan functional programming (FP). Kedua
paradigma tersebut akan sangat membantu proses pengembangan dengan masing-
masing fitur yang dimilikinya.
3. Selain multiparadigm, Kotlin juga mendukung multiplatform yang berbeda dengan
bahasa pemrograman mainstream lainnya di mana kita dapat membuat aplikasi
mobile (iOS/Android), Web, Desktop, atau Server. Bahkan Kotlin digadang-gadang
bisa digunakan untuk Deep Learning dengan KotlinDL yang saat ini sudah berstatus
alpha!
4. Kotlin pernah berada di posisi pertama dalam Fastest growing languages
versi GitHub Octoverse 2018 berkat banyaknya dukungan komunitas dalam
pengembangannya.
5. Tersedia berbagai macam framework yang bisa Anda gunakan untuk mempermudah
pengembangan aplikasi server-side menggunakan Kotlin seperti:

o Spring
Spring merupakan sebuah framework yang sangat terkenal di Java. Spring
bisa digunakan pada Kotlin untuk komunikasi ke API dengan lebih ringkas.
Tersedia juga Spring Initializr yang memungkinkan kita untuk membuat
proyek Spring baru dengan Kotlin.
o Vert.x
Merupakan sebuah framework untuk membuat reactive Web app di JVM.
Anda bisa melihat repository-nya di https://github.com/vert-x3/vertx-lang-
kotlin.
o Ktor
Ktor adalah sebuah framework yang dikembangkan oleh JetBrains untuk
membuat aplikasi Web di Kotlin. Ktor memanfaatkan coroutine untuk
skalabilitas yang tinggi dan menawarkan API yang mudah digunakan.
o Kotlinx.html
Merupakan sebuah DSL yang dapat digunakan untuk membuat HTML di
aplikasi Web. Kotlinx.html dapat digunakan sebagai alternatif untuk sistem
templating tradisional seperti JSP dan FreeMarker.
o Exposed
Sebuah framework SQL yang menyediakan kumpulan DSL yang mudah
dibaca untuk menggambarkan struktur database SQL dan melakukan kueri
sepenuhnya dengan Kotlin.
6. Kotlin mendukung dengan baik dan memiliki beberapa kelebihan dalam
mengembangkan aplikasi Android seperti di bawah ini:

o Compatibility
Kotlin sepenuhnya kompatibel dengan JDK 6. Ini memastikan bahwa aplikasi
yang dibangun dengan Kotlin dapat berjalan pada perangkat Android yang
lebih lama tanpa ada masalah. Android Studio pun mendukung penuh
pengembangan dengan bahasa Kotlin.
o Performance
Dengan struktur bytecode yang sama dengan Java, aplikasi yang dibangun
dengan Kotlin dapat berjalan setara dengan aplikasi yang dibangun dengan
Java. Terdapat juga fitur seperti inline function pada Kotlin yang membuat
kode yang dituliskan dengan lambda bisa berjalan lebih cepat dibandingkan
kode yang sama dan dituliskan dengan Java.
o Interoperability
Semua library Android yang tersedia, dapat digunakan pada Kotlin.
o Compilation Time
Kotlin mendukung kompilasi inkremental yang efisien. Oleh karena itu, proses
build biasanya sama atau lebih cepat dibandingkan dengan Java.
7. “Safe” merupakan salah satu karakteristik Kotlin yang sangat berguna di mana Anda
bisa meminimalisir kesalahan NullPointerException.
Rangkuman di atas merupakan awal dari perjalanan Anda dalam menyelesaikan kelas ini.
Jadi, tetap semangat untuk menuntaskan modul-modul berikutnya ya!

Rangkuman dari Persiapan Membangun dan Menjalankan Program


Kotlin
Kita sudah selesai dengan persiapan membangun dan menjalankan program dengan Kotlin.
Untuk me-refresh kembali apa yang sudah dipelajari, berikut adalah rangkumannya:

 Kotlin menggunakan Java Development Kit (JDK) sebagai SDK-nya.


 Terdapat banyak distribusi JDK yang bisa Anda gunakan
seperti OracleJDK, OpenJDK atau Azul Zulu JDK.
 Kotlin berjalan di atas dan mengunakan JVM untuk menjalankan program yang Anda
sudah kembangkan.
 Untuk melakukan otomatisasi seperti proses kompilasi, Anda akan menggunakan
Gradle sebagai build script dalam pengembangan program.
 Anda bisa menggunakan Groovy atau Kotlin DSL sebagai gradle build script.
 Dalam proses instalasi JDK dan Gradle di Linux dan macOS, Anda akan
memanfaatkan SDK Manager yang bernama SDKMAN.
 Sedangkan pada OS Windows, menggunakan installer yang sudah disediakan Azul
Zulu JDK.
 Dalam menuliskan kode program, Anda bisa memanfaatkan IDE yang dikembangkan
oleh JetBrains, yaitu Intellij IDEA yang terbagi atas 2 (dua) versi. Yang pertama
versi Ultimate dan Community yang dapat digunakan secara gratis.
 Untuk membuat projek Kotlin, bisa dilakukan dengan 2 (dua) cara yaitu melalui
terminal atau langsung menggunakan IntelliJ IDEA dengan project wizard-nya.
Setelah melakukan persiapan, sudah siap menuliskan kode Kotlin? Yuk kita lanjut ke modul
berikutnya.

Sebelum Anda lanjut ke modul berikutnya, berikut adalah beberapa rangkuman dari sub-
modul yang sudah dipelajari:
 Sama seperti bahasa pemrograman lain, Kotlin memiliki fungsi untuk mencetak nilai
pada console yaitu fungsi print() dan println().
 Untuk mendeklarasi variable, Anda akan menggunakan kata kunci var ata val. var
atau val digunakan untuk mengontrol nilai dari sebuah variabel. Dengan kata kunci
var kita bisa mengubah nilai yang sudah kita inisialisasikan.
 Untuk membuat variable yang menampung data berupa text, Anda dapat
menggunakan tipe data Char untuk menyimpan satu karakter dan tipe
data String untuk menyimpan beberapa karakter.
 Sedangkan untuk menampung data berupa number, Anda akan menggunakan
beberapa tipe data di bawah ini:
o Int (32 Bit)
Int adalah tipe data yang umumnya digunakan untuk menyimpan nilai
numerik. Int dapat menyimpan data dari range -2^31 sampai +2^31-1.
Dengan ukuran 32 Bit kita bisa menggunakannya untuk menyimpan nilai
yang besar. Catatannya, tetap lihatlah batasan nilai maksimal yang dapat
dimasukkan.
o Long (64 Bit)
Long adalah tipe data yang digunakan untuk menyimpan nilai numerik yang
lebih besar yaitu dari range -2^63 sampai +2^63-1.
o Short (16 Bit)
Short merupakan sebuah bilangan bulat yang hanya dapat menyimpan nilai
yang kecil karena hanya berukuran 16 Bit.
o Byte (8 Bit)
Dengan ukuran yang kecil, Byte hanya mampu menyimpan nilai yang kecil
sama halnya seperti Short. Byte biasa digunakan untuk keperluan proses
membaca dan menulis data dari sebuah stream file atau jaringan.
o Double (64 Bit)
Sama halnya dengan Long yang memiliki ukuran yang besar, Double mampu
menyimpan nilai numerik yang besar pula. Pada umumnya Double digunakan
untuk menyimpan nilai numerik pecahan.
 Selanjutnya adalah Array, yakni tipe data yang memungkinkan Anda untuk
menyimpan beberapa objek di dalam satu variabel.
 Kotlin juga memungkinkan Anda untuk membuat Array dengan tipe data primitif
dengan memanfaatkan beberapa fungsi spesifik seperti berikut:
o intArrayOf() : IntArray
o booleanArrayOf() : BooleanArray
o charArrayOf() : CharArray
o longArrayOf() : LongArray
o shortArrayOf() : ShortArray
o byteArrayOf() : ByteArray
 Kotlin mendukung juga tipe data Boolean di mana tipe data yang hanya memiliki dua
nilai, yaitu true dan false. Selain itu, Terdapat 3 (tiga) operator yang dapat digunakan
pada Boolean:
o Conjunction atau AND (&&)
Operator AND (&&) akan mengembalikan nilai true jika semua hasil evaluasi
expression yang diberikan bernilai true.
o Disjunction atau OR (||)
Berbeda dengan operator AND (&&), operator OR (||) akan mengembalikan
nilai true jika hasil evaluasi dari salah satu expressions yang diberikan
bernilai true.
o Negation atau NOT (!)
Berbeda dengan operator AND (&&) dan operator OR(||), operator NOT(!)
digunakan untuk melakukan negasi pada hasil evaluasi expression yang
diberikan.
 Jika ingin menginisialisasi nilai dari sebuah variabel berdasarkan suatu kondisi.
Untuk menyelesaikannya, gunakan If Expression.
 Dengan Kotlin kita mudah dalam mengelola variable nullable sehingga dapat
meminimalisir terjadinya NullPointerException dengan menggunakan Safe
Call dan Elvis Operator.
 Function atau fungsi merupakan sebuah prosedur yang memiliki keterkaitan dengan
pesan dan objek. Ketika kita memanggil sebuah fungsi maka sebuah mini-
program akan dijalankan. Fungsi sendiri bisa diartikan sebagai cara sederhana untuk
mengatur program buatan kita.

Kita sudah selesai dengan Kotlin Fundamental di mana Anda sudah mempelajari tentang
bagaimana menampilkan pesan pada konsol, berbagai macam tipe data, dan bagaimana
cara meminimalisir terjadinya NullPointerException. Selanjutnya kita mempelajari modul
Control Flow. Langsung saja ke modul berikutnya.

Rangkuman dari Control Flow


Sudah bisa menentukan alur program yang akan dikembangkan? Yuk kita ulas lagi apa
yang sudah kita pelajari pada modul Control Flow:

 Untuk menghindari penggunaan konstan yang keliru, kita bisa memanfaatkan fitur
Enumeration untuk menyimpan kumpulan objek yang telah didefinisikan menjadi tipe
data konstanta.
 Jika memiliki beberapa ekspresi untuk menentukan hasil evaluasi, Anda bisa
menggunakan when expression. Karena sebuah expression, when dapat
mengembalikan nilai yang dapat ditampung pada sebuah variabel.
 Sama seperti when, if expression dapat mengembalikkan nilai yang dapat ditampung
pada sebuah variabel. Namun sedikit berbeda dengan when, if lebih cocok
digunakan jika ekspresi yang akan digunakan untuk dievaluasi hanya 1 (satu).
 Dalam mempelajari bahasa pemgrograman, Anda akan sering menjumpai istilah
expressions dan statement. 
 Jika ingin melakukan perulangan, ada beberapa cara yang dapat diterapkan yaitu:

o While
While bersifat Entry Controlled Loop. Artinya, kondisi yang diberikan akan
dievaluasi terlebih dahulu. Jika kondisi tersebut terpenuhi maka proses
perulangan akan dijalankan.
o Do While
Do While bersifat Exit Controlled Loop di mana proses perulangan akan
langsung dijalankan di awal. Jika telah selesai, barulah kondisi yang diberikan
akan dievaluasi.
o For Loop
For merupakan konsep perulangan pada blok yang sama selama hasil
evaluasi kondisi yang diberikan terpenuhi atau bernilai true. For
memanfaatkan tipe data Range untuk menentukan kondisi yang akan
dievaluasi.
 Saat menggunakan While dan Do While perhatikan infinite loop, yaitu kondisi di
mana proses perulangan berlangsung terus menerus sampai aplikasi menjadi crash.
 Saat menerapkan perulangan, kita bisa memanfaatkan kata
kunci break dan continue. Kedua kata kunci tersebut digunakan untuk menentukan
proses perulangan akan seperti apa di mana break digunakan untuk menghentikan
proses perulangan walaupun hasil evaluasi masih menghasil true dan continue
digunakan untuk melanjutkan proses perulangan selanjutnya.
Data Classes
Pada modul ini, kita akan mempelajari sebuah fitur menarik pada Kotlin, yaitu Data Classes.

Kotlin mengenalkan konsep data class yang merupakan sebuah kelas sederhana yang bisa
berperan sebagai data container. Data class adalah sebuah kelas yang tidak memiliki logika
apapun dan juga tidak memiliki fungsionalitas lain selain menangani data.

Kenapa disebut dengan kelas sederhana? Seperti yang sudah kita ketahui, Kotlin
memungkinkan kita untuk menulis kode dengan ringkas dan lebih efisien. Dalam membuat
sebuah data class, kita tidak perlu menuliskan banyak kode yang seharusnya dibutuhkan
untuk mengelola sebuah data. Data class mampu menyediakan beberapa fungsionalitas
yang biasanya kita butuhkan untuk mengelola data hanya dengan sebuah keyword data.

1. data class User(val name : String, val age : Int)

Hanya dengan satu baris kode di atas, kompiler akan secara otomatis
menghasilkan constructor, toString(), equals(), hashCode(), copy() dan juga
fungsi componentN(). Tentunya ini jauh lebih mudah dan bersih dibandingkan kita harus
menuliskan banyak kode secara manual.

Beberapa hal yang perlu diperhatikan dalam membuat sebuah data class adalah:

 Konstruktor utama pada kelas tersebut harus memiliki setidaknya satu parameter;
 Semua konstruktor utama perlu dideklarasikan sebagai val atau var;
 Modifier dari sebuah data class tidak bisa abstract, open, sealed, atau inner.
Rangkuman dari Kotlin Functional Programming
Sebelum lanjut ke paradigma Object-Oriented Programming, mari kita rangkum apa saja
yang sudah Anda pelajari pada modul Functional Programming.

 Anatomi dari sebuah function terdiri dari 2 (adua) bagian utama, yaitu function
header dan function body:

o Function Header
Function header adalah bagian yang merupakan konstruksi dari sebuah
fungsi untuk menentukan perilakunya akan seperti apa. Di dalam function
header terdapat visibility modifier, kata kunci fun, nama, daftar parameter dan
nilai kembalian dari fungsi tersebut.
o Function Body
Function Body adalah bagian yang dalamnya kita akan menempatkan sebuah
logika kode baik itu sebuah expression atau statement.
 Kotlin memiliki fitur seperti named dan default argument yang dapat menghindari kita
dari kesalahan saat menyematkan sebuah argumen saat menggunakan sebuah
fungsi.
 Selain named dan default argument, Kotlin juga memiliki fitur varargs yang dapat
digunakan untuk menyederhanakan deklarasi beberapa parameter yang memiliki tipe
yang sama.
 Kotlin mendukung 2 (dua) extension yang dapat digunakan, yaitu Extension
Functions dan Extension Properties. Jika extension functions digunakan untuk
menambahkan fungsi baru, extension properties tentunya digunakan untuk
menambahkan sebuah properti baru.
 Lambda expression, atau biasa disebut dengan anonymous function atau function
literal adalah fitur yang cukup populer sampai sekarang dalam dunia functional
programming. Bisa disebut sebagai anonymous karena lambda tidak memiliki
sebuah nama seperti halnya sebuah fungsi pada umumnya. Karena merupakan
sebuah fungsi, lambda juga dapat memiliki daftar parameter, body dan return type.
Berikut adalah beberapa karakteristik dari Lambda pada kotlin.

o Dalam menggunakan lambda, kita tidak perlu mendeklarasi tipe spesifik


untuk nilai kembaliannya. Tipe tersebut akan ditentukan oleh kompiler secara
otomatis.
o Walaupun merupakan sebuah fungsi, lambda tidak membutuhkan kata kunci
fun dan visibility modifier saat dideklarasikan, karena lambda bersifat
anonymous.
o Parameter yang akan ditetapkan berada di dalam kurung kurawal {}. 
o Ketika ingin mengembalikan nilai, kata kunci return tidak diperlukan lagi
karena kompiler akan secara otomatis mengembalikan nilai dari dalam body.
o Lambda expression dapat digunakan sebagai argumen untuk sebuah
parameter dan dapat disimpan ke dalam sebuah variabel.
 Higher-Order Function, yaitu sebuah fungsi yang menggunakan fungsi lainnya
sebagai parameter, menjadikan tipe kembalian, ataupun keduanya. High-order
function adalah salah satu fitur yang memanfaatkan Lambda dan .
 Kotlin standard library memiliki beberapa fungsi yang tujuan utamanya adalah
bagaimana menuliskan logika kode di dalam konteks dari sebuah objek. Dalam
menggunakan fungsi tersebut kita akan memanfaatkan lambda expression yang
memiliki ruang lingkupnya sendiri, di mana dalam ruang lingkup tersebut kita dapat
mengakses konteks dari sebuah objek. Fungsi inilah yang dinamakan sebagai scope
function. Beberapa fungsi yang berada di dalamnya antara lain, let, run, with, apply,
dan also.
 Sama seperti Java, Kotlin  mendukung juga mekanisme reflection yang berarti
seperangkat fitur bahasa dan library yang memungkinkan kita untuk mengamati
struktur kode dari proyek yang sedang kita kerjakan secara langsung.
 Saat menggunakan Kotlin, kita bisa mendeklarasi fungsi di dalam sebuah fungsi dan
menggunakannya hanya di  dalam fungsi tersebut.
 Kotlin Collection adalah salah satu struktur data mumpuni yang banyak menyediakan
fungsi untuk memudahkan kita dalam mengelola dan memanipulasi data. Pada
modul-modul sebelumnya, kita sudah mempelajari beberapa fungsi yang disediakan
seperti map(), sum(), dan sorted(). Selain beberapa fungsi yang sudah disebutkan,
berikut fungsi lain yang dapat kita manfaatkan:

o Fold
Fold, Anda bisa dengan mudah melakukan perhitungan setiap nilai yang
berada di dalam sebuah collection tanpa harus melakukan iterasi item
tersebut satu-persatu menggunakan fungsi fold().
o Drop
drop(), fungsi yang bisa kita manfaatkan untuk memangkas item yang berada
di dalam sebuah objek collection berdasarkan jumlah yang kita tentukan.
o Take
Fungsi take() bisa kita manfaatkan untuk menyaring item yang berada di
dalam sebuah objek collection.
o Slice
Bagaimana jika kita ingin menyaring item dari posisi tertentu? Untuk itu Anda
bisa memanfaatkan fungsi slice().
o Distinct
Saat berurusan dengan item yang sama di dalam sebuah collection, untuk
menyaring item yang sama tersebut kita akan melakukan iterasi dan
membandingkan setiap itemnya. Namun dengan Kotlin kita tidak perlu
melakukannya secara manual, karena Kotlin Collection menyediakan fungsi
untuk melakukannya dengan mudah yaitu fungsi distinct().
o Chunked
Sama seperti fungsi split(), fungsi chunked() bisa kita gunakan untuk
memecah nilai String menjadi beberapa bagian kecil dalam bentuk Array.
 Recursion merupakan sebuah teknik dasar dalam pemrograman yang bisa kita
gunakan untuk menyederhanakan pemecahan masalah yang umumnya diselesaikan
dengan cara yang kompleks. Di Kotlin, recursion disebut juga dengan recursive
function.

Rangkuman dari Kotlin Object-Oriented Programming


Okay, kita sudah selesai belajar dan mengetahui mengenai paradigma Object-Oriented
pada Kotlin. Untuk me-refresh apa yang sudah kita pelajari, mari kita merangkum beberapa
hal penting mengenai paradigma ini.

 Object-Oriented Programming masih menjadi salah satu paradigma atau teknik


pemrograman yang banyak digunakan dalam pengembangan aplikasi.
 Gambaran umum dari Object-Oriented Programming adalah sebuah blueprint, di
mana blueprint ini adalah sebuah Class (kelas).
 Pada Object-Oriented Programming terdapat beberapa komponen penting
yaitu Object, Class, Attribute dan Behaviour.
 Object atau Objek merupakan hasil realisasi dari sebuah blueprint atau class yang
tentunya memiliki fungsi dan juga properti sama seperti blueprint-nya. Salah satu
contoh Object yang sering kita pakai adalah nilai primitif pada Kotlin,
yaitu String, Integer, Char dan Boolean. Kegunaan dari Objek sendiri adalah untuk
mengakses berbagai properti dan fungsi pada kelas.
 Class merupakan sebuah blueprint. Setiap kelas memiliki atribut dan behaviour.
Dalam Kotlin, attributes lebih sering disebut dengan properties,
sedangkan behaviour sering disebut functions. Berikut penjelasan singkat dari setiap
komponen:

o Class: Merupakan sebuah blueprint yang terdapat properti dan fungsi di


dalamnya.
o Properties: Karakteristik dari sebuah kelas, memiliki tipe data.
o Functions: Kemampuan atau aksi dari sebuah kelas.
 Properti dapat dideklarasikan sebagai nilai mutable dengan menggunakan var atau
sebagai nilai read-only dengan menggunakan val. 
 Konstruktor merupakan fungsi spesial yang digunakan untuk menginisialisasi properti
yang terdapat pada sebuah kelas.
 Terdapat 3 (tiga) tipe konstruktor pada Kotlin, yaitu primary
constructor, secondary constructor dan default constructor. 

o Primary Constructor

 Seperti namanya, jika kita akan membuat suatu objek dari sebuah
kelas dan kelas tersebut memiliki constructor di dalamnya, maka
konstruktor tersebut adalah primary constructor dan diharuskan untuk
mengirim nilai sesuai properti yang dibutuhkan.
 Primary Constructor juga dapat memiliki nilai default, dengan begitu
jika kita tidak menetapkan nilai untuk parameter tersebut maka
properti tersebut akan memiliki nilai default.
 Kotlin menyediakan blok init yang memungkinkan kita untuk
menuliskan properti di dalam body class ketika kita
menggunakan primary constructor. 
o Secondary Constructor

 Secondary constructor digunakan ketika kita ingin menginisialisasi


sebuah kelas dengan cara yang lain. 
 Dapat memiliki lebih dari satu secondary constructor.
o Default Constructor

 Kotlin secara otomatis membuat sebuah default constructor pada


kelas jika kita tidak membuat sebuah konstruktor secara manual.
 Pada Object-Oriented Programming terdapat Inheritance/Pewarisan. Inheritance
dapat mencegah kita melakukan perulangan kode dengan cara mengelompokkan
properti dan fungsi yang sama. Pada Inheritance, terdapat parent class dan
juga child class.
 Abstract Class merupakan gambaran umum dari sebuah kelas. Abstract Class tidak
dapat direalisasikan dalam sebuah objek.
 Visibility Modifier adalah hak akses pada Kotlin. Dengan menentukan hak akses
tersebut, kita dapat membatasi akses data pada sebuah kelas. Berikut macam-
macam hak akses dan penjelasan singkatnya yang dapat digunakan pada Kotlin:

o Public: Hak akses yang cakupannya paling luas. Anggota yang


diberi modifier ini dapat diakses dari manapun.
o Private: Hak akses yang cakupannya paling terbatas. Anggota yang
menerapkannya hanya dapat diakses pada scope yang sama.
o Protected: Hak akses yang cakupannya terbatas pada hirarki kelas. Anggota
hanya dapat diakses pada kelas turunannya atau kelas itu sendiri.
o Internal: Hak akses yang cakupannya terbatas pada satu modul. Anggota
yang menggunakannya tidak dapat diakses diluar dari modulnya tersebut.
 Seluruh konten pada Kotlin, seperti kelas dan fungsi, dibungkus dalam
sebuah package. Package tersebut digunakan untuk mengelompokkan kelas, fungsi
dan variabel yang mempunyai kemiripan fungsionalitas. 
 Exception adalah event (kejadian) yang dapat mengacaukan jalannya suatu
program. Pada Kotlin semua exception bersifat Unchecked, yang
artinya exception terjadi karena kesalahan pada kode kita. Kode yang baik yaitu
kode yang terhindar dari segala bentuk kejadian dengan efek buruk pada aplikasi
kita. Kejadian tersebut pada programming disebut Exception. Hal seperti ini
seharusnya dihindari. Oleh karena itu kita harus mengetahui cara menangani
suatu exception (Exception Handling).
 Berikut ini beberapa contoh Unchecked Exception yang sering mengganggu jalannya
program kita:

o ArithmeticException

 Merupakan exception yang terjadi karena kita membagi suatu


bilangan dengan nilai nol.
o NumberFormatException

 Disebabkan karena terjadi kesalahan dalam format angka.


o NullPointerException
 Terjadi karena sebuah variabel atau objek memiliki nilai null, padahal
seharusnya objek atau variabel tersebut tidak boleh null.
 Exception Handling dapat diterapkan dengan beberapa cara. Di antaranya adalah
dengan menggunakan try-catch, try-catch-finally, dan multiple catch.
 Pada Kotlin menggunakan dua atau lebih fungsi dengan nama yang sama disebut
dengan overloading. Overloading dapat dilakukan selama fungsi itu memiliki
parameter yang berbeda.
 Extension properties pada Kotlin sama halnya seperti melakukannya pada
Extension function. Kita dapat menambahkan sebuah properti tanpa harus membuat
sebuah kelas yang mewarisi kelas tersebut. Hal ini dilakukan dengan deklarasi
khusus yang disebut dengan Extension.
 Interfaces merupakan suatu konsep sifat umum yang nantinya digunakan oleh suatu
kelas agar dapat memiliki sifat tersebut.
 Teknik membuat sebuah kelas yang memang bertugas untuk mengatur atau
mengelola fungsi getter dan setter untuk sebuah properti kelas pada Kotlin
dinamakan Delegate.

Rangkuman dari Kotlin Generics


Inti yang telah dipelajari pada modul ini adalah Generics, sebuah konsep yang
memungkinkan suatu kelas atau interface menjadi tipe parameter yang dapat digunakan
untuk berbagai macam tipe data. Tidak hanya itu saja, kita telah mengenal juga
tentang variance, yaitu salah satu konsep dari generics. Berikut adalah rangkuman dari
keseluruhan materi tersebut :

 Generics pada kotlin dapat diterapkan dengan meletakkan tipe parameter ke dalam


angle brackets (<>). Tipe parameter atau alias ini merupakan word yang bisa bebas
Anda deklarasikan, contoh yang paling umum adalah menggunakan word  ‘T’.

1. interface List<T>{
2.     operator fun get(index: Int) : T
3. }

 Ada 2 jenis penerapan generics, yaitu Generics pada Class, dan Generics pada


Function.
 Generics pada Class dilakukan dengan meletakkan tipe parameter setelah
penulisan nama class.

1. interface List<T>{
2.     operator fun get(index: Int) : T
3. }

 Class yang mewarisi sebuah Generic Class atau Interface harus menentukan tipe


argumen sebagai tipe dasar dari parameter generic kelas tersebut. Tipe parameter
yang digunakan bisa berupa tipe yang spesifik (Integer, Float, Double, atau Object
lainnya) atau bisa juga class tersebut mendeklarasikan tipe parameter yang lainnya.
 Generics pada Function dilakukan dengan meletakkan tipe parameter sebelum
penulisan nama dari fungsi tersebut.

1. fun <T> run(): T {
2.     /*...*/
3. }

 Dalam menggunakan sebuah class maupun function generic, tipe parameter


diletakkan setelah memanggil nama dari sebuah function ataupun class.
1. fun main() {
2.     val longArrayList = ArrayList<Long>()
3.     displayArray<Long>(longArrayList)
4. }

 Constraint Type Parameter adalah teknik untuk membatasi cakupan tipe data apa
saja yang dapat di sediakan oleh Generic Class maupun Generic Function.
Pembatasan ini dilakukan dengan membubuhkan tanda colon (:) setelah tipe
parameter yang kemudian diikuti oleh tipe data yang akan dijadikan batasan.
 Batasan dalam Constraint Type Parameter adalah child class dari tipe data
batasan tersebut. Jadi, semisal ditentukan batasannya adalah tipe List, maka hanya
berlaku pada child class dari class tersebut.
 Variance, adalah konsep pada Generics yang menggambarkan bagaimana sebuah
tipe yang memiliki subtipe yang sama dan tipe argumen yang berbeda saling
berkaitan satu sama lain. Ada beberapa jenis dari variance yaitu: 

o Covariant
Ditandai dengan keyword ‘out’ sebelum deklarasi dari tipe parameter. Nilai
dari tipe parameter tersebut hanya bisa diproduksi seperti menjadikanya
sebagai return type dan tidak dapat dikonsumsi seperti menjadikannya
sebagai tipe argumen untuk setiap fungsi di dalam kelas tersebut.
o Contravariant
Ditandai dengan keyword ‘in’ sebelum deklarasi dari tipe parameter. Nilai dari
tipe parameter tersebut hanya bisa dikonsumsi seperti menjadikannya
sebagai tipe argumen untuk setiap fungsi dan tidak untuk dikonsumsi
sebagai return type dari sebuah function di dalam kelas tersebut.

Rangkuman dari Berkenalan dengan Coroutines


Di modul ini, kita telah membahas tentang apa itu concurrency, pararelism, threading,
bahkan penerapannya pada kotlin dengan menggunakan fitur Coroutines. Cukup menarik
dan mudah ‘kan ? Oke, mari kita ingat - ingat kembali apa saja yang telah dipelajari hingga
saat ini:

 Concurrency adalah beberapa proses yang terjadi pada suatu sistem. Terjadi apabila
terdapat 2 (dua) atau lebih proses yang tumpang tindih dalam satu waktu.
 Parallelism, sama seperti concurrency, namun 2 (dua) atau lebih proses tersebut
dijalankan pada waktu yang sama persis.
 Ada beberapa konsep dasar dari penerapan concurrency yang harus dipelajari
yaitu:

o Process
Merupakan bagian dari proses aplikasi yang dijalankan. Setiap kali aplikasi
dijalankan, maka saat itu juga proses dijalankan. Tergantung pada sistem
operasi yang digunakan, suatu proses dapat terdiri dari beberapa thread yang
menjalankan instruksi secara bersamaan.
o Thread
Biasa dikenal sebagai proses yang ringan dan merupakan komponen dari
suatu proses aplikasi yang menjalankan tugas tertentu secara spesifik.
o I/O-Bound
Sebuah algoritma yang bergantung pada perangkat input atau output. Waktu
untuk mengeksekusi sebuah I/O-bound tergantung pada kecepatan
perangkat yang digunakan.
 Ada beberapa permasalahan yang ditimbulkan ketika
menerapkan concurrency pada aplikasi, yaitu :
o Deadlocks
Sebuah kondisi di mana dua proses atau lebih saling menunggu proses yang
lain untuk melepaskan resource yang sedang digunakan yang
mengakibatkan proses yang sedang berjalan tak kunjung selesai melakukan
operasinya.
o Livelocks
Kondisi di mana sebuah proses tidak bisa melanjutkan tugasnya, sama
seperti deadlocks. Namun, perbedaannya adalah selama livelocks terjadi,
keadaan aplikasi tetap bisa berubah. Walau perubahan keadaan tersebut
menyebabkan proses berjalan dengan tidak semestinya.
o Starvation
Kondisi yang biasa terjadi setelah deadlock. Kondisi ini sering kali
menyebabkan sebuah proses kekurangan sumber daya. Pada kondisi ini,
thread menjadi tidak mendapatkan akses reguler ke sumber daya bersama
dan membuat proses terhenti.
o Race Conditions
Kondisi di mana terdapat banyak thread yang mengakses data yang
dibagikan bersama dan mencoba mengubahnya secara bersamaan. Ini bisa
terjadi ketika kode concurrent yang dituliskan untuk berjalan secara
sekuensial.
 Coroutines merupakan fitur unggulan dan cara baru dalam menuliskan thread yang
ringan dan efisien pada Kotlin. Disediakan JetBrains khusus untuk Kotlin.
 Untuk menjalankan coroutine, diperlukan fungsi yang biasa disebut
sebagai coroutine builder yang mengambil suspending lambda dan membuat
coroutine untuk menjalankannya. Ada beberapa macam coroutine builder yaitu:

o launch
Fungsi ini digunakan untuk memulai sebuah coroutines yang tidak akan
mengembalikan sebuah hasil dan menghasilkan Job yang dapat digunakan
untuk membatalkan eksekusi.
o runBlocking
Dibuat untuk menjembatani blocking code menjadi kode yang dapat
ditangguhkan. runBlocking akan memblokir sebuah thread yang sedang
berjalan hingga eksekusi coroutine selesai
o async
Fungsi ini digunakan untuk memulai sebuah coroutine yang akan
mengembalikan sebuah hasil.
 Job, merupakan hasil dari perintah asynchronous yang dijalankan dan
merepresentasikan coroutine yang sebenarnya. Memiliki 3 properti terdiri
dari isActive, isCompleted, dan isCancelled yang
menunjukkan state dari Job tersebut. 
 Deferred, adalah nilai kembalian dari fungsi async yang dapat berupa hasil atau
exception. Merupakan nilai tangguhan yang dihasilkan dari proses coroutines dan
bisa kita kelola sesuai dengan kebutuhan.
 CoroutineDispatcher, merupakan base class yang menentukan thread yang
berfungsi untuk menjalankan coroutines. Class ini diimplementasikan pada beberapa
class berikut :

o Dispatcher.Default
Merupakan dispatcher dasar yang digunakan oleh semua standard builders
seperti launch, async, dll jika tidak ada dispatcher lain yang
ditentukan. Dispatcher.Default menggunakan kumpulan thread yang ada
pada JVM. Jumlah maksimal thread yang digunakan adalah sama dengan
jumlah core dari CPU. 
o Dispatcher.IO
Merupakan dispatcher yang digunakan untuk membongkar pemblokiran
operasi I/O dan akan menggunakan kumpulan thread yang dibuat
berdasarkan permintaan.
o Dispatcher.Unconfined
Merupakan Dispatcher yang menjalankan coroutines pada thread yang
sedang berjalan sampai mencapai titik penangguhan. Setelah penangguhan,
coroutines akan dilanjutkan pada thread dimana komputasi penangguhan
yang dipanggil.
 Selain dengan menggunakan metode diatas, ada beberapa builder lain yang dapat
digunakan untuk menentukan thread yang dibutuhkan, antara lain:

o Single Thread Context


Dispatcher ini menjamin bahwa setiap saat coroutine akan dijalankan pada
thread yang ditentukan. Penerapannya dilakukan dengan memanfaatkan
fungsi newSingleThreadContext() pada parameter dispatcher dalam
fungsi launch().
o Thread Pool
Sebuah dispatcher yang memiliki kumpulan thread. Ia akan memulai dan
melanjutkan coroutine di salah satu thread yang tersedia pada kumpulan
tersebut. Runtime akan menentukan thread mana yang tersedia dan juga
menentukan bagaimana proses distribusi bebannya. Penerapannya dilakukan
dengan memanfaatkan fungsi newFixedThreadPoolContext() pada
parameter dispatcher dalam fungsi launch().
 Channel,  adalah adalah nilai deferred yang menyediakan cara mudah untuk
mentransfer nilai tunggal antara coroutine atau bisa disebut melakukan interaksi
antar coroutine serta membuat kode concurrent dapat berkomunikasi dengan
mengirim dan menerima pesan tanpa harus peduli di thread mana coroutine
berjalan..

Anda mungkin juga menyukai