Kotlin Bootcamp for Programmers 4: Pemrograman berorientasi objek

Codelab ini adalah bagian dari kursus Kotlin Bootcamp for Programmers. Anda akan mendapatkan manfaat maksimal dari kursus ini jika menyelesaikan codelab secara berurutan. Anda mungkin dapat membaca cepat beberapa bagian, bergantung pada pengetahuan Anda. Kursus ini ditujukan bagi programer yang menguasai bahasa berorientasi objek, dan ingin mempelajari Kotlin.

Pengantar

Dalam codelab ini, Anda akan membuat program Kotlin, lalu mempelajari class dan objek di Kotlin. Sebagian besar konten ini akan terasa tidak asing bagi Anda jika Anda menguasai bahasa berorientasi objek lain, tetapi Kotlin memiliki beberapa perbedaan penting untuk mengurangi jumlah kode yang perlu Anda tulis. Anda juga akan mempelajari class abstrak dan delegasi antarmuka.

Daripada membuat satu aplikasi contoh, pelajaran dalam kursus ini dirancang untuk membangun pengetahuan Anda, tetapi bersifat semi-independen satu sama lain sehingga Anda dapat membaca sekilas bagian yang sudah Anda kuasai. Untuk mengaitkannya, banyak contoh yang menggunakan tema akuarium. Jika Anda ingin melihat kisah akuarium selengkapnya, lihat kursus Kotlin Bootcamp for Programmers di Udacity.

Yang harus sudah Anda ketahui

  • Dasar-dasar Kotlin, termasuk jenis, operator, dan perulangan
  • Sintaksis fungsi Kotlin
  • Dasar-dasar pemrograman berorientasi objek
  • Dasar-dasar IDE seperti IntelliJ IDEA atau Android Studio

Yang akan Anda pelajari

  • Cara membuat class dan mengakses properti di Kotlin
  • Cara membuat dan menggunakan konstruktor class di Kotlin
  • Cara membuat subclass, dan cara kerja pewarisan
  • Tentang class abstrak, antarmuka, dan delegasi antarmuka
  • Cara membuat dan menggunakan class data
  • Cara menggunakan singleton, enum, dan class tertutup

Yang akan Anda lakukan

  • Membuat class dengan properti
  • Membuat konstruktor untuk class
  • Membuat subclass
  • Periksa contoh class abstrak dan antarmuka
  • Membuat class data sederhana
  • Mempelajari singleton, enum, dan class tertutup

Istilah pemrograman berikut seharusnya sudah tidak asing bagi Anda:

  • Class adalah cetak biru untuk objek. Misalnya, class Aquarium adalah blueprint untuk membuat objek akuarium.
  • Objek adalah instance class; objek akuarium adalah salah satu Aquarium aktual.
  • Properti adalah karakteristik class, seperti panjang, lebar, dan tinggi Aquarium.
  • Metode, yang juga disebut fungsi anggota, adalah fungsi class. Metode adalah hal yang dapat Anda "lakukan" dengan objek. Misalnya, Anda dapat fillWithWater() objek Aquarium.
  • Antarmuka adalah spesifikasi yang dapat diimplementasikan oleh class. Misalnya, pembersihan umum untuk objek selain akuarium, dan pembersihan umumnya terjadi dengan cara yang serupa untuk objek yang berbeda. Jadi, Anda dapat memiliki antarmuka bernama Clean yang menentukan metode clean(). Class Aquarium dapat mengimplementasikan antarmuka Clean untuk membersihkan akuarium dengan spons lembut.
  • Paket adalah cara untuk mengelompokkan kode terkait agar tetap teratur, atau untuk membuat library kode. Setelah paket dibuat, Anda dapat mengimpor konten paket ke file lain dan menggunakan kembali kode dan class di dalamnya.

Dalam tugas ini, Anda akan membuat paket dan class baru dengan beberapa properti dan metode.

Langkah 1: Buat paket

Paket dapat membantu Anda mengatur kode.

  1. Di panel Project, di project Hello Kotlin, klik kanan folder src.
  2. Pilih New > Package, lalu beri nama example.myapp.

Langkah 2: Buat class dengan properti

Class ditentukan dengan kata kunci class, dan nama class berdasarkan konvensi dimulai dengan huruf kapital.

  1. Klik kanan paket example.myapp.
  2. Pilih New > Kotlin File / Class.
  3. Di bagian Kind, pilih Class, dan beri nama class Aquarium. IntelliJ IDEA menyertakan nama paket dalam file dan membuat class Aquarium kosong untuk Anda.
  4. Di dalam class Aquarium, tentukan dan lakukan inisialisasi properti var untuk lebar, tinggi, dan panjang (dalam sentimeter). Lakukan inisialisasi properti dengan nilai default.
package example.myapp

class Aquarium {
    var width: Int = 20
    var height: Int = 40
    var length: Int = 100
}

Di balik layar, Kotlin secara otomatis membuat pengambil dan penyetel untuk properti yang Anda tentukan di class Aquarium, sehingga Anda dapat mengakses properti secara langsung, misalnya, myAquarium.length.

Langkah 3: Buat fungsi main()

Buat file baru bernama main.kt untuk menyimpan fungsi main().

  1. Di panel Project di sebelah kiri, klik kanan paket example.myapp.
  2. Pilih New > Kotlin File / Class.
  3. Di dropdown Jenis, biarkan pilihan sebagai File, dan beri nama file main.kt. IntelliJ IDEA menyertakan nama paket, tetapi tidak menyertakan definisi class untuk file.
  4. Tentukan fungsi buildAquarium() dan di dalamnya buat instance Aquarium. Untuk membuat instance, referensikan class seolah-olah itu adalah fungsi, Aquarium(). Tindakan ini memanggil konstruktor class dan membuat instance class Aquarium, mirip dengan penggunaan new dalam bahasa lain.
  5. Tentukan fungsi main() dan panggil buildAquarium().
package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium()
}

fun main() {
    buildAquarium()
}

Langkah 4: Tambahkan metode

  1. Di class Aquarium, tambahkan metode untuk mencetak properti dimensi akuarium.
    fun printSize() {
        println("Width: $width cm " +
                "Length: $length cm " +
                "Height: $height cm ")
    }
  1. Di main.kt, di buildAquarium(), panggil metode printSize() di myAquarium.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
}
  1. Jalankan program dengan mengklik segitiga hijau di samping fungsi main(). Amati hasilnya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
  1. Di buildAquarium(), tambahkan kode untuk menyetel tinggi menjadi 60 dan mencetak properti dimensi yang diubah.
fun buildAquarium() {
    val myAquarium = Aquarium()
    myAquarium.printSize()
    myAquarium.height = 60
    myAquarium.printSize()
}
  1. Jalankan program Anda dan amati output-nya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 100 cm Height: 60 cm 

Dalam tugas ini, Anda akan membuat konstruktor untuk class, dan melanjutkan pekerjaan dengan properti.

Langkah 1: Buat konstruktor

Pada langkah ini, Anda akan menambahkan konstruktor ke class Aquarium yang Anda buat pada tugas pertama. Dalam contoh sebelumnya, setiap instance Aquarium dibuat dengan dimensi yang sama. Anda dapat mengubah dimensi setelah dibuat dengan menetapkan properti, tetapi akan lebih mudah untuk membuat ukuran yang tepat sejak awal.

Dalam beberapa bahasa pemrograman, konstruktor ditentukan dengan membuat metode dalam class yang memiliki nama yang sama dengan class. Di Kotlin, Anda menentukan konstruktor langsung dalam deklarasi class itu sendiri, dengan menentukan parameter di dalam tanda kurung seolah-olah class adalah metode. Seperti fungsi di Kotlin, parameter tersebut dapat menyertakan nilai default.

  1. Di class Aquarium yang Anda buat sebelumnya, ubah definisi class untuk menyertakan tiga parameter konstruktor dengan nilai default untuk length, width, dan height, lalu tetapkan ke properti yang sesuai.
class Aquarium(length: Int = 100, width: Int = 20, height: Int = 40) {
   // Dimensions in cm
   var length: Int = length
   var width: Int = width
   var height: Int = height
...
}
  1. Cara Kotlin yang lebih ringkas adalah dengan menentukan properti secara langsung dengan konstruktor, menggunakan var atau val, dan Kotlin juga membuat getter dan setter secara otomatis. Kemudian, Anda dapat menghapus definisi properti di isi class.
class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40) {
...
}
  1. Saat membuat objek Aquarium dengan konstruktor tersebut, Anda dapat menentukan tanpa argumen dan mendapatkan nilai default, atau menentukan beberapa di antaranya, atau menentukan semuanya dan membuat Aquarium berukuran sepenuhnya kustom. Pada fungsi buildAquarium(), coba berbagai cara untuk membuat objek Aquarium menggunakan parameter bernama.
fun buildAquarium() {
    val aquarium1 = Aquarium()
    aquarium1.printSize()
    // default height and length
    val aquarium2 = Aquarium(width = 25)
    aquarium2.printSize()
    // default width
    val aquarium3 = Aquarium(height = 35, length = 110)
    aquarium3.printSize()
    // everything custom
    val aquarium4 = Aquarium(width = 25, height = 35, length = 110)
    aquarium4.printSize()
}
  1. Jalankan program dan amati output-nya.
⇒ Width: 20 cm Length: 100 cm Height: 40 cm 
Width: 25 cm Length: 100 cm Height: 40 cm 
Width: 20 cm Length: 110 cm Height: 35 cm 
Width: 25 cm Length: 110 cm Height: 35 cm 

Perhatikan bahwa Anda tidak perlu membebani konstruktor dan menulis versi yang berbeda untuk setiap kasus ini (plus beberapa lagi untuk kombinasi lainnya). Kotlin membuat apa yang diperlukan dari nilai default dan parameter bernama.

Langkah 2: Tambahkan blok init

Konstruktor contoh di atas hanya mendeklarasikan properti dan menetapkan nilai ekspresi ke properti tersebut. Jika konstruktor Anda memerlukan lebih banyak kode inisialisasi, kode tersebut dapat ditempatkan dalam satu atau beberapa blok init. Pada langkah ini, Anda akan menambahkan beberapa blok init ke class Aquarium.

  1. Di class Aquarium, tambahkan blok init untuk mencetak bahwa objek sedang diinisialisasi, dan blok kedua untuk mencetak volume dalam liter.
class Aquarium (var length: Int = 100, var width: Int = 20, var height: Int = 40) {
    init {
        println("aquarium initializing")
    }
    init {
        // 1 liter = 1000 cm^3
        println("Volume: ${width * length * height / 1000} l")
    }
}
  1. Jalankan program dan amati output-nya.
aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 100 l
Width: 25 cm Length: 100 cm Height: 40 cm 
aquarium initializing
Volume: 77 l
Width: 20 cm Length: 110 cm Height: 35 cm 
aquarium initializing
Volume: 96 l
Width: 25 cm Length: 110 cm Height: 35 cm 

Perhatikan bahwa blok init dijalankan dalam urutan kemunculannya dalam definisi class, dan semuanya dijalankan saat konstruktor dipanggil.

Langkah 3: Pelajari konstruktor sekunder

Pada langkah ini, Anda akan mempelajari konstruktor sekunder dan menambahkannya ke kelas Anda. Selain konstruktor utama, yang dapat memiliki satu atau beberapa blok init, class Kotlin juga dapat memiliki satu atau beberapa konstruktor sekunder untuk memungkinkan kelebihan beban konstruktor, yaitu konstruktor dengan argumen yang berbeda.

  1. Di class Aquarium, tambahkan konstruktor sekunder yang mengambil sejumlah ikan sebagai argumennya, menggunakan kata kunci constructor. Buat properti tangki val untuk volume akuarium yang dihitung dalam liter berdasarkan jumlah ikan. Asumsikan 2 liter (2.000 cm^3) air per ikan, ditambah ruang ekstra agar air tidak tumpah.
constructor(numberOfFish: Int) : this() {
    // 2,000 cm^3 per fish + extra room so water doesn't spill
    val tank = numberOfFish * 2000 * 1.1
}
  1. Di dalam konstruktor sekunder, pertahankan panjang dan lebar (yang ditetapkan dalam konstruktor utama) yang sama, dan hitung tinggi yang diperlukan untuk membuat tangki dengan volume yang ditentukan.
    // calculate the height needed
    height = (tank / (length * width)).toInt()
  1. Pada fungsi buildAquarium(), tambahkan panggilan untuk membuat Aquarium menggunakan konstruktor sekunder baru Anda. Cetak ukuran dan volume.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    println("Volume: ${aquarium6.width * aquarium6.length * aquarium6.height / 1000} l")
}
  1. Jalankan program Anda dan amati output-nya.
⇒ aquarium initializing
Volume: 80 l
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Perhatikan bahwa volume dicetak dua kali, sekali oleh blok init di konstruktor utama sebelum konstruktor sekunder dieksekusi, dan sekali oleh kode di buildAquarium().

Anda juga dapat menyertakan kata kunci constructor di konstruktor utama, tetapi hal ini tidak diperlukan dalam sebagian besar kasus.

Langkah 4: Tambahkan pengambil properti baru

Pada langkah ini, Anda akan menambahkan getter properti eksplisit. Kotlin secara otomatis menentukan pengambil dan penyetel saat Anda menentukan properti, tetapi terkadang nilai untuk properti perlu disesuaikan atau dihitung. Misalnya, di atas, Anda mencetak volume Aquarium. Anda dapat membuat volume tersedia sebagai properti dengan menentukan variabel dan pengambil untuknya. Karena volume perlu dihitung, pengambil perlu menampilkan nilai yang dihitung, yang dapat Anda lakukan dengan fungsi satu baris.

  1. Di class Aquarium, tentukan properti Int yang disebut volume, dan tentukan metode get() yang menghitung volume di baris berikutnya.
val volume: Int
    get() = width * height * length / 1000  // 1000 cm^3 = 1 l
  1. Hapus blok init yang mencetak volume.
  2. Hapus kode di buildAquarium() yang mencetak volume.
  3. Dalam metode printSize(), tambahkan baris untuk mencetak volume.
fun printSize() {
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm "
    )
    // 1 l = 1000 cm^3
    println("Volume: $volume l")
}
  1. Jalankan program Anda dan amati output-nya.
⇒ aquarium initializing
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l

Dimensi dan volume sama seperti sebelumnya, tetapi volume hanya dicetak satu kali setelah objek diinisialisasi sepenuhnya oleh konstruktor utama dan konstruktor sekunder.

Langkah 5: Tambahkan setter properti

Pada langkah ini, Anda membuat setter properti baru untuk volume.

  1. Di class Aquarium, ubah volume menjadi var sehingga dapat ditetapkan lebih dari sekali.
  2. Tambahkan setter untuk properti volume dengan menambahkan metode set() di bawah getter, yang menghitung ulang tinggi berdasarkan jumlah air yang diberikan. Menurut konvensi, nama parameter setter adalah value, tetapi Anda dapat mengubahnya jika mau.
var volume: Int
    get() = width * height * length / 1000
    set(value) {
        height = (value * 1000) / (width * length)
    }
  1. Di buildAquarium(), tambahkan kode untuk menyetel volume Akuarium menjadi 70 liter. Cetak ukuran baru.
fun buildAquarium() {
    val aquarium6 = Aquarium(numberOfFish = 29)
    aquarium6.printSize()
    aquarium6.volume = 70
    aquarium6.printSize()
}
  1. Jalankan program Anda lagi dan amati perubahan tinggi dan volume.
⇒ aquarium initialized
Width: 20 cm Length: 100 cm Height: 31 cm 
Volume: 62 l
Width: 20 cm Length: 100 cm Height: 35 cm 
Volume: 70 l

Sejauh ini belum ada pengubah visibilitas, seperti public atau private, dalam kode. Hal ini karena secara default, semua hal di Kotlin bersifat publik, yang berarti semua hal dapat diakses di mana saja, termasuk class, metode, properti, dan variabel anggota.

Di Kotlin, class, objek, antarmuka, konstruktor, fungsi, properti, dan penyetelnya dapat memiliki pengubah visibilitas:

  • public berarti terlihat ke luar class. Semuanya merupakan publik secara default, termasuk variabel dan metode class.
  • internal berarti hanya akan terlihat dalam modul tersebut. Modul adalah sekumpulan file Kotlin yang dikompilasi bersama, misalnya, library atau aplikasi.
  • private berarti hanya akan terlihat di dalam class (atau file sumber jika Anda bekerja dengan fungsi).
  • protected sama seperti private, tetapi juga akan terlihat di subclass mana pun.

Lihat Pengubah Visibilitas dalam dokumentasi Kotlin untuk mengetahui informasi selengkapnya.

Variabel anggota

Properti dalam class, atau variabel anggota, adalah public secara default. Jika Anda menentukannya dengan var, class data tersebut dapat diubah, yaitu dapat dibaca dan ditulis. Jika Anda menentukannya dengan val, nilai tersebut bersifat hanya baca setelah inisialisasi.

Jika Anda menginginkan properti yang dapat dibaca atau ditulis oleh kode Anda, tetapi hanya dapat dibaca oleh kode di luar, Anda dapat membiarkan properti dan getter-nya sebagai publik serta mendeklarasikan setter-nya sebagai pribadi, seperti yang ditunjukkan di bawah.

var volume: Int
    get() = width * height * length / 1000
    private set(value) {
        height = (value * 1000) / (width * length)
    }

Dalam tugas ini, Anda akan mempelajari cara kerja subclass dan pewarisan di Kotlin. Tampilannya mirip dengan yang Anda lihat dalam bahasa lain, tetapi ada beberapa perbedaan.

Di Kotlin, secara default, class tidak dapat dibuat subclass. Demikian pula, properti dan variabel anggota tidak dapat diganti oleh subclass (meskipun dapat diakses).

Anda harus menandai class sebagai open agar dapat dibuat subclass-nya. Demikian pula, Anda harus menandai properti dan variabel anggota sebagai open, untuk menggantinya di subclass. Kata kunci open diperlukan untuk mencegah kebocoran detail implementasi secara tidak sengaja sebagai bagian dari antarmuka class.

Langkah 1: Buka kelas Akuarium

Pada langkah ini, Anda akan membuat class Aquarium menjadi open, sehingga Anda dapat menggantinya pada langkah berikutnya.

  1. Tandai class Aquarium dan semua propertinya dengan kata kunci open.
open class Aquarium (open var length: Int = 100, open var width: Int = 20, open var height: Int = 40) {
    open var volume: Int
        get() = width * height * length / 1000
        set(value) {
            height = (value * 1000) / (width * length)
        }
  1. Tambahkan properti shape terbuka dengan nilai "rectangle".
   open val shape = "rectangle"
  1. Tambahkan properti water terbuka dengan getter yang menampilkan 90% volume Aquarium.
    open var water: Double = 0.0
        get() = volume * 0.9
  1. Tambahkan kode ke metode printSize() untuk mencetak bentuk, dan jumlah air sebagai persentase volume.
fun printSize() {
    println(shape)
    println("Width: $width cm " +
            "Length: $length cm " +
            "Height: $height cm ")
    // 1 l = 1000 cm^3
    println("Volume: $volume l Water: $water l (${water/volume*100.0}% full)")
}
  1. Di buildAquarium(), ubah kode untuk membuat Aquarium dengan width = 25, length = 25, dan height = 40.
fun buildAquarium() {
    val aquarium6 = Aquarium(length = 25, width = 25, height = 40)
    aquarium6.printSize()
}
  1. Jalankan program Anda dan amati output baru.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)

Langkah 2: Buat subclass

  1. Buat subclass Aquarium bernama TowerTank, yang mengimplementasikan tangki silinder bulat, bukan tangki persegi panjang. Anda dapat menambahkan TowerTank di bawah Aquarium, karena Anda dapat menambahkan class lain dalam file yang sama dengan class Aquarium.
  2. Di TowerTank, ganti properti height, yang ditentukan dalam konstruktor. Untuk mengganti properti, gunakan kata kunci override di subclass.
  1. Buat konstruktor untuk TowerTank mengambil diameter. Gunakan diameter untuk length dan width saat memanggil konstruktor di superclass Aquarium.
class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
  1. Ganti properti volume untuk menghitung silinder. Rumus untuk tabung adalah pi kali jari-jari pangkat dua kali tinggi. Anda perlu mengimpor konstanta PI dari java.lang.Math.
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }
  1. Di TowerTank, ganti properti water menjadi 80% dari volume.
override var water = volume * 0.8
  1. Ganti shape menjadi "cylinder".
override val shape = "cylinder"
  1. Class TowerTank akhir Anda akan terlihat seperti kode di bawah.

Aquarium.kt:

package example.myapp

import java.lang.Math.PI

... // existing Aquarium class

class TowerTank (override var height: Int, var diameter: Int): Aquarium(height = height, width = diameter, length = diameter) {
    override var volume: Int
    // ellipse area = π * r1 * r2
    get() = (width/2 * length/2 * height / 1000 * PI).toInt()
    set(value) {
        height = ((value * 1000 / PI) / (width/2 * length/2)).toInt()
    }

    override var water = volume * 0.8
    override val shape = "cylinder"
}
  1. Di buildAquarium(), buat TowerTank dengan diameter 25 cm dan tinggi 45 cm. Cetak ukuran.

main.kt:

package example.myapp

fun buildAquarium() {
    val myAquarium = Aquarium(width = 25, length = 25, height = 40)
    myAquarium.printSize()
    val myTower = TowerTank(diameter = 25, height = 40)
    myTower.printSize()
}
  1. Jalankan program Anda dan amati output-nya.
⇒ aquarium initializing
rectangle
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 25 l Water: 22.5 l (90.0% full)
aquarium initializing
cylinder
Width: 25 cm Length: 25 cm Height: 40 cm 
Volume: 18 l Water: 14.4 l (80.0% full)

Terkadang Anda ingin menentukan perilaku atau properti umum yang akan dibagikan di antara beberapa class terkait. Kotlin menawarkan dua cara untuk melakukannya, yaitu antarmuka dan class abstrak. Dalam tugas ini, Anda akan membuat class AquariumFish abstrak untuk properti yang umum untuk semua ikan. Anda membuat antarmuka yang disebut FishAction untuk menentukan perilaku umum semua ikan.

  • Class abstrak maupun antarmuka tidak dapat dibuat instance-nya sendiri, yang berarti Anda tidak dapat membuat objek jenis tersebut secara langsung.
  • Class abstrak memiliki konstruktor.
  • Antarmuka tidak dapat memiliki logika konstruktor atau menyimpan status apa pun.

Langkah 1. Membuat class abstrak

  1. Di bagian example.myapp, buat file baru, AquariumFish.kt.
  2. Buat class, yang juga disebut AquariumFish, dan tandai dengan abstract.
  3. Tambahkan satu properti String, color, dan tandai dengan abstract.
package example.myapp

abstract class AquariumFish {
    abstract val color: String
}
  1. Buat dua subclass AquariumFish, Shark, dan Plecostomus.
  2. Karena color bersifat abstrak, subclass harus menerapkannya. Buat Shark berwarna abu-abu dan Plecostomus berwarna emas.
class Shark: AquariumFish() {
    override val color = "gray"
}

class Plecostomus: AquariumFish() {
    override val color = "gold"
}
  1. Di main.kt, buat fungsi makeFish() untuk menguji class Anda. Buat instance Shark dan Plecostomus, lalu cetak warna masing-masing.
  2. Hapus kode pengujian sebelumnya di main() dan tambahkan panggilan ke makeFish(). Kode Anda akan terlihat seperti kode di bawah.

main.kt:

package example.myapp

fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()

    println("Shark: ${shark.color}")
    println("Plecostomus: ${pleco.color}")
}

fun main () {
    makeFish()
}
  1. Jalankan program Anda dan amati output-nya.
⇒ Shark: gray 
Plecostomus: gold

Diagram berikut merepresentasikan class Shark dan class Plecostomus, yang merupakan subclass dari class abstrak, AquariumFish.

Diagram yang menampilkan class abstrak, AquariumFish, dan dua subclass, Shark dan Plecostomus.

Langkah 2. Membuat antarmuka

  1. Di AquariumFish.kt, buat antarmuka bernama FishAction dengan metode eat().
interface FishAction  {
    fun eat()
}
  1. Tambahkan FishAction ke setiap subclass, dan terapkan eat() dengan membuatnya mencetak apa yang dilakukan ikan.
class Shark: AquariumFish(), FishAction {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

class Plecostomus: AquariumFish(), FishAction {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Di fungsi makeFish(), buat setiap ikan yang Anda buat memakan sesuatu dengan memanggil eat().
fun makeFish() {
    val shark = Shark()
    val pleco = Plecostomus()
    println("Shark: ${shark.color}")
    shark.eat()
    println("Plecostomus: ${pleco.color}")
    pleco.eat()
}
  1. Jalankan program Anda dan amati output-nya.
⇒ Shark: gray
hunt and eat fish
Plecostomus: gold
eat algae

Diagram berikut menampilkan class Shark dan class Plecostomus, yang keduanya terdiri dari dan menerapkan antarmuka FishAction.

Kapan harus menggunakan class abstrak versus antarmuka

Contoh di atas sederhana, tetapi jika Anda memiliki banyak class yang saling terkait, class abstrak dan antarmuka dapat membantu Anda menjaga desain tetap bersih, lebih teratur, dan lebih mudah dikelola.

Seperti yang disebutkan di atas, class abstrak dapat memiliki konstruktor, dan antarmuka tidak dapat, tetapi keduanya sangat mirip. Jadi, kapan Anda harus menggunakan masing-masingnya?

Saat Anda menggunakan antarmuka untuk menyusun class, fungsi class diperluas melalui instance class yang dikandungnya. Komposisi cenderung membuat kode lebih mudah digunakan kembali dan dipahami daripada pewarisan dari class abstrak. Selain itu, Anda dapat menggunakan beberapa antarmuka dalam satu class, tetapi Anda hanya dapat membuat subclass dari satu class abstrak.

Komposisi sering kali menghasilkan enkapsulasi yang lebih baik, coupling (interdependensi) yang lebih rendah, antarmuka yang lebih bersih, dan kode yang lebih mudah digunakan. Karena alasan ini, menggunakan komposisi dengan antarmuka adalah desain yang lebih disukai. Di sisi lain, pewarisan dari class abstrak cenderung cocok secara alami untuk beberapa masalah. Jadi, Anda harus lebih memilih komposisi, tetapi jika pewarisan masuk akal, Kotlin juga memungkinkan Anda melakukannya.

  • Gunakan antarmuka jika Anda memiliki banyak metode dan satu atau dua implementasi default, misalnya seperti pada AquariumAction di bawah.
interface AquariumAction {
    fun eat()
    fun jump()
    fun clean()
    fun catchFish()
    fun swim()  {
        println("swim")
    }
}
  • Gunakan class abstrak setiap kali Anda tidak dapat menyelesaikan class. Misalnya, dengan kembali ke class AquariumFish, Anda dapat membuat semua AquariumFish menerapkan FishAction, dan memberikan implementasi default untuk eat sambil membiarkan color abstrak, karena sebenarnya tidak ada warna default untuk ikan.
interface FishAction  {
    fun eat()
}

abstract class AquariumFish: FishAction {
   abstract val color: String
   override fun eat() = println("yum")
}

Tugas sebelumnya memperkenalkan class abstrak, antarmuka, dan ide komposisi. Delegasi antarmuka adalah teknik lanjutan di mana metode antarmuka diimplementasikan oleh objek helper (atau delegasi), yang kemudian digunakan oleh class. Teknik ini dapat berguna saat Anda menggunakan antarmuka dalam serangkaian class yang tidak terkait: Anda menambahkan fungsi antarmuka yang diperlukan ke class helper terpisah, dan setiap class menggunakan instance class helper untuk mengimplementasikan fungsi.

Dalam tugas ini, Anda menggunakan delegasi antarmuka untuk menambahkan fungsi ke class.

Langkah 1: Buat antarmuka baru

  1. Di AquariumFish.kt, hapus class AquariumFish. Daripada mewarisi dari class AquariumFish, Plecostomus dan Shark akan menerapkan antarmuka untuk tindakan ikan dan warnanya.
  2. Buat antarmuka baru, FishColor, yang menentukan warna sebagai string.
interface FishColor {
    val color: String
}
  1. Ubah Plecostomus untuk menerapkan dua antarmuka, FishAction, dan FishColor. Anda harus mengganti color dari FishColor dan eat() dari FishAction.
class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}
  1. Ubah class Shark Anda agar juga menerapkan dua antarmuka, FishAction dan FishColor, bukan mewarisi dari AquariumFish.
class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}
  1. Kode yang sudah selesai akan terlihat seperti ini:
package example.myapp

interface FishAction {
    fun eat()
}

interface FishColor {
    val color: String
}

class Plecostomus: FishAction, FishColor {
    override val color = "gold"
    override fun eat() {
        println("eat algae")
    }
}

class Shark: FishAction, FishColor {
    override val color = "gray"
    override fun eat() {
        println("hunt and eat fish")
    }
}

Langkah 2: Buat class singleton

Selanjutnya, Anda menerapkan penyiapan untuk bagian delegasi dengan membuat class helper yang menerapkan FishColor. Anda membuat class dasar bernama GoldColor yang mengimplementasikan FishColor—yang dilakukannya hanyalah menyatakan bahwa warnanya adalah emas.

Membuat beberapa instance GoldColor tidak masuk akal, karena semuanya akan melakukan hal yang sama persis. Jadi, Kotlin memungkinkan Anda mendeklarasikan class yang hanya dapat membuat satu instance dengan menggunakan kata kunci object, bukan class. Kotlin akan membuat satu instance tersebut, dan instance tersebut dirujuk oleh nama class. Kemudian, semua objek lain dapat menggunakan satu instance ini—tidak ada cara untuk membuat instance lain dari class ini. Jika Anda sudah terbiasa dengan pola singleton, begini cara menerapkan singleton di Kotlin.

  1. Di AquariumFish.kt, buat objek untuk GoldColor. Ganti warna.
object GoldColor : FishColor {
   override val color = "gold"
}

Langkah 3: Tambahkan delegasi antarmuka untuk FishColor

Sekarang Anda siap menggunakan delegasi antarmuka.

  1. Di AquariumFish.kt, hapus penggantian color dari Plecostomus.
  2. Ubah class Plecostomus untuk mendapatkan warnanya dari GoldColor. Anda melakukannya dengan menambahkan by GoldColor ke deklarasi class, membuat delegasi. Artinya, alih-alih menerapkan FishColor, gunakan penerapan yang disediakan oleh GoldColor. Jadi, setiap kali color diakses, aksesnya didelegasikan ke GoldColor.
class Plecostomus:  FishAction, FishColor by GoldColor {
   override fun eat() {
       println("eat algae")
   }
}

Dengan kelas seperti ini, semua Pleco akan berwarna emas, tetapi ikan ini sebenarnya memiliki banyak warna. Anda dapat mengatasi hal ini dengan menambahkan parameter konstruktor untuk warna dengan GoldColor sebagai warna default untuk Plecostomus.

  1. Ubah class Plecostomus agar mengambil fishColor yang diteruskan dengan konstruktornya, dan tetapkan default-nya ke GoldColor. Ubah delegasi dari by GoldColor menjadi by fishColor.
class Plecostomus(fishColor: FishColor = GoldColor):  FishAction,
       FishColor by fishColor {
   override fun eat() {
       println("eat algae")
   }
}

Langkah 4: Tambahkan delegasi antarmuka untuk FishAction

Dengan cara yang sama, Anda dapat menggunakan delegasi antarmuka untuk FishAction.

  1. Di AquariumFish.kt, buat class PrintingFishAction yang mengimplementasikan FishAction, yang mengambil String, food, lalu mencetak apa yang dimakan ikan.
class PrintingFishAction(val food: String) : FishAction {
    override fun eat() {
        println(food)
    }
}
  1. Di class Plecostomus, hapus fungsi penggantian eat(), karena Anda akan menggantinya dengan delegasi.
  2. Dalam deklarasi Plecostomus, delegasikan FishAction ke PrintingFishAction, dengan meneruskan "eat algae".
  3. Dengan semua delegasi tersebut, tidak ada kode di isi class Plecostomus, jadi hapus {}, karena semua penggantian ditangani oleh delegasi antarmuka
class Plecostomus (fishColor: FishColor = GoldColor):
        FishAction by PrintingFishAction("eat algae"),
        FishColor by fishColor

Diagram berikut menunjukkan class Shark dan Plecostomus, yang keduanya terdiri dari antarmuka PrintingFishAction dan FishColor, tetapi mendelegasikan implementasinya kepada mereka.

Delegasi antarmuka sangat berguna, dan Anda harus mempertimbangkan cara menggunakannya setiap kali Anda mungkin menggunakan class abstrak dalam bahasa lain. Dengan komposisi, Anda dapat memasukkan perilaku, bukan memerlukan banyak subkelas, yang masing-masing dikhususkan dengan cara yang berbeda.

Class data mirip dengan struct di beberapa bahasa lain—class ini ada terutama untuk menyimpan beberapa data—tetapi objek class data tetap merupakan objek. Objek class data Kotlin memiliki beberapa manfaat tambahan, seperti utilitas untuk mencetak dan menyalin. Dalam tugas ini, Anda akan membuat class data sederhana dan mempelajari dukungan yang diberikan Kotlin untuk class data.

Langkah 1: Buat class data

  1. Tambahkan paket decor baru di bagian paket example.myapp untuk menyimpan kode baru. Klik kanan example.myapp di panel Project, lalu pilih File > New > Package.
  2. Dalam paket, buat class baru bernama Decoration.
package example.myapp.decor

class Decoration {
}
  1. Untuk menjadikan Decoration class data, awali deklarasi class dengan kata kunci data.
  2. Tambahkan properti String bernama rocks untuk memberikan beberapa data ke class.
data class Decoration(val rocks: String) {
}
  1. Di file, di luar class, tambahkan fungsi makeDecorations() untuk membuat dan mencetak instance Decoration dengan "granite".
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)
}
  1. Tambahkan fungsi main() untuk memanggil makeDecorations(), lalu jalankan program Anda. Perhatikan output yang masuk akal yang dibuat karena ini adalah class data.
⇒ Decoration(rocks=granite)
  1. Di makeDecorations(), buat dua objek Decoration lagi yang keduanya "slate" dan cetak.
fun makeDecorations() {
    val decoration1 = Decoration("granite")
    println(decoration1)

    val decoration2 = Decoration("slate")
    println(decoration2)

    val decoration3 = Decoration("slate")
    println(decoration3)
}
  1. Di makeDecorations(), tambahkan pernyataan cetak yang mencetak hasil perbandingan decoration1 dengan decoration2, dan yang kedua membandingkan decoration3 dengan decoration2. Gunakan metode equals() yang disediakan oleh class data.
    println (decoration1.equals(decoration2))
    println (decoration3.equals(decoration2))
  1. Jalankan kode.
⇒ Decoration(rocks=granite)
Decoration(rocks=slate)
Decoration(rocks=slate)
false
true

Langkah 2. Menggunakan destrukturisasi

Untuk mendapatkan properti objek data dan menetapkannya ke variabel, Anda dapat menetapkannya satu per satu, seperti ini.

val rock = decoration.rock
val wood = decoration.wood
val diver = decoration.diver

Sebagai gantinya, Anda dapat membuat variabel, satu untuk setiap properti, dan menetapkan objek data ke grup variabel. Kotlin menempatkan nilai properti di setiap variabel.

val (rock, wood, diver) = decoration

Tindakan ini disebut destrukturisasi dan merupakan singkatan yang berguna. Jumlah variabel harus cocok dengan jumlah properti, dan variabel ditetapkan dalam urutan yang dideklarasikan dalam class. Berikut contoh lengkap yang dapat Anda coba di Decoration.kt.

// Here is a data class with 3 properties.
data class Decoration2(val rocks: String, val wood: String, val diver: String){
}

fun makeDecorations() {
    val d5 = Decoration2("crystal", "wood", "diver")
    println(d5)

// Assign all properties to variables.
    val (rock, wood, diver) = d5
    println(rock)
    println(wood)
    println(diver)
}
⇒ Decoration2(rocks=crystal, wood=wood, diver=diver)
crystal
wood
diver

Jika tidak memerlukan satu atau beberapa properti, Anda dapat melewatinya dengan menggunakan _, bukan nama variabel, seperti yang ditunjukkan dalam kode di bawah.

    val (rock, _, diver) = d5

Dalam tugas ini, Anda akan mempelajari beberapa class khusus di Kotlin, termasuk:

  • Class singleton
  • Enum
  • Kelas tertutup

Langkah 1: Panggil kembali class singleton

Ingat kembali contoh sebelumnya dengan class GoldColor.

object GoldColor : FishColor {
   override val color = "gold"
}

Karena setiap instance GoldColor melakukan hal yang sama, instance tersebut dideklarasikan sebagai object, bukan sebagai class, untuk menjadikannya singleton. Hanya boleh ada satu instance.

Langkah 2: Buat enum

Kotlin juga mendukung enum, yang memungkinkan Anda menghitung sesuatu dan merujuknya berdasarkan nama, seperti dalam bahasa lain. Deklarasikan enum dengan mengawali deklarasi dengan kata kunci enum. Deklarasi enum dasar hanya memerlukan daftar nama, tetapi Anda juga dapat menentukan satu atau beberapa kolom yang terkait dengan setiap nama.

  1. Di Decoration.kt, coba contoh enum.
enum class Color(val rgb: Int) {
   RED(0xFF0000), GREEN(0x00FF00), BLUE(0x0000FF);
}

Enum sedikit mirip singleton—hanya boleh ada satu, dan hanya boleh ada satu dari setiap nilai dalam enumerasi. Misalnya, hanya boleh ada satu Color.RED, satu Color.GREEN, dan satu Color.BLUE. Dalam contoh ini, nilai RGB ditetapkan ke properti rgb untuk merepresentasikan komponen warna. Anda juga bisa mendapatkan nilai ordinal enum menggunakan properti ordinal, dan namanya menggunakan properti name.

  1. Coba contoh enum lain.
enum class Direction(val degrees: Int) {
    NORTH(0), SOUTH(180), EAST(90), WEST(270)
}

fun main() {
    println(Direction.EAST.name)
    println(Direction.EAST.ordinal)
    println(Direction.EAST.degrees)
}
⇒ EAST
2
90

Langkah 3: Buat class tertutup

Sealed class adalah class yang dapat dibuat subclass-nya, tetapi hanya di dalam file tempat class tersebut dideklarasikan. Jika Anda mencoba membuat subclass class dalam file yang berbeda, Anda akan mendapatkan error.

Karena class dan subclass berada dalam file yang sama, Kotlin akan mengetahui semua subclass secara statis. Artinya, pada waktu kompilasi, compiler melihat semua class dan subclass serta mengetahui bahwa ini adalah semuanya, sehingga compiler dapat melakukan pemeriksaan tambahan untuk Anda.

  1. Di AquariumFish.kt, coba contoh class tertutup, dengan tetap menggunakan tema akuatik.
sealed class Seal
class SeaLion : Seal()
class Walrus : Seal()

fun matchSeal(seal: Seal): String {
   return when(seal) {
       is Walrus -> "walrus"
       is SeaLion -> "sea lion"
   }
}

Class Seal tidak dapat dibuat subclass di file lain. Jika ingin menambahkan lebih banyak jenis Seal, Anda harus menambahkannya dalam file yang sama. Hal ini menjadikan class tertutup sebagai cara yang aman untuk merepresentasikan sejumlah tetap jenis. Misalnya, class tertutup sangat cocok untuk menampilkan keberhasilan atau error dari API jaringan.

Pelajaran ini mencakup banyak hal. Meskipun sebagian besar akan terasa familiar dari bahasa pemrograman berorientasi objek lainnya, Kotlin menambahkan beberapa fitur untuk menjaga kode tetap ringkas dan mudah dibaca.

Class dan konstruktor

  • Tentukan class di Kotlin menggunakan class.
  • Kotlin secara otomatis membuat penyetel dan pengambil untuk properti.
  • Tentukan konstruktor utama secara langsung dalam definisi class. Contoh:
    class Aquarium(var length: Int = 100, var width: Int = 20, var height: Int = 40)
  • Jika konstruktor utama memerlukan kode tambahan, tulis di satu atau beberapa blok init.
  • Class dapat menentukan satu atau beberapa konstruktor sekunder menggunakan constructor, tetapi gaya Kotlin adalah menggunakan fungsi factory.

Pengubah visibilitas dan subclass

  • Semua class dan fungsi di Kotlin bersifat public secara default, tetapi Anda dapat menggunakan pengubah untuk mengubah visibilitas menjadi internal, private, atau protected.
  • Untuk membuat subclass, class induk harus ditandai open.
  • Untuk mengganti metode dan properti di subclass, metode dan properti harus ditandai open di class induk.
  • Class tertutup hanya dapat dibuat subclass dalam file yang sama tempat class tersebut ditentukan. Buat class tertutup dengan mengawali deklarasi dengan sealed.

Class data, singleton, dan enum

  • Buat class data dengan mengawali deklarasi dengan data.
  • Destructuring adalah singkatan untuk menetapkan properti objek data ke variabel terpisah.
  • Buat class singleton dengan menggunakan object, bukan class.
  • Tentukan enum menggunakan enum class.

Class abstrak, antarmuka, dan delegasi

  • Class dan antarmuka abstrak adalah dua cara untuk berbagi perilaku umum antar-class.
  • Class abstrak menentukan properti dan perilaku, tetapi menyerahkan implementasi ke subclass.
  • Antarmuka menentukan perilaku, dan dapat memberikan implementasi default untuk beberapa atau semua perilaku.
  • Saat Anda menggunakan antarmuka untuk menyusun class, fungsi class diperluas melalui instance class yang dikandungnya.
  • Delegasi antarmuka menggunakan komposisi, tetapi juga mendelegasikan penerapan ke class antarmuka.
  • Komposisi adalah cara efektif untuk menambahkan fungsi ke class menggunakan delegasi antarmuka. Secara umum, komposisi lebih disukai, tetapi pewarisan dari class abstrak lebih cocok untuk beberapa masalah.

Dokumentasi Kotlin

Jika Anda ingin mendapatkan informasi lebih lanjut tentang topik apa pun dalam kursus ini, atau jika Anda mengalami masalah, https://kotlinlang.org adalah titik awal terbaik Anda.

Tutorial Kotlin

Situs https://try.kotlinlang.org menyertakan tutorial lengkap yang disebut Kotlin Koans, interpreter berbasis web, dan serangkaian lengkap dokumentasi referensi dengan contoh.

Kursus Udacity

Untuk melihat kursus Udacity tentang topik ini, lihat Kotlin Bootcamp for Programmers.

IntelliJ IDEA

Dokumentasi untuk IntelliJ IDEA dapat ditemukan di situs JetBrains.

Bagian ini mencantumkan kemungkinan tugas pekerjaan rumah untuk siswa yang mengerjakan codelab ini sebagai bagian dari kursus yang dipimpin oleh instruktur. Instruktur menentukan hal berikut:

  • Memberikan pekerjaan rumah jika diperlukan.
  • Memberi tahu siswa cara mengirimkan tugas pekerjaan rumah.
  • Memberi nilai tugas pekerjaan rumah.

Instruktur bisa menggunakan saran ini sesuai kebutuhan, dan bebas menugaskan pekerjaan rumah lain yang dirasa cocok.

Jika Anda menyelesaikan codelab ini sendiri, gunakan tugas pekerjaan rumah ini untuk menguji pengetahuan Anda.

Jawab pertanyaan-pertanyaan berikut

Pertanyaan 1

Class memiliki metode khusus yang berfungsi sebagai cetak biru untuk membuat objek class tersebut. Apa nama metodenya?

▢ Builder

▢ Instantiator

▢ Konstruktor

▢ Cetak biru

Pertanyaan 2

Manakah dari pernyataan berikut tentang antarmuka dan class abstrak yang TIDAK benar?

▢ Class abstrak dapat memiliki konstruktor.

▢ Antarmuka tidak dapat memiliki konstruktor.

▢ Antarmuka dan class abstrak dapat di-instantiate secara langsung.

▢ Properti abstrak harus diterapkan oleh subclass dari class abstrak.

Pertanyaan 3

Manakah dari berikut ini yang BUKAN merupakan pengubah visibilitas Kotlin untuk properti, metode, dll.?

internal

nosubclass

protected

private

Pertanyaan 4

Pertimbangkan class data ini:
data class Fish(val name: String, val species:String, val colors:String)
Manakah dari kode berikut yang BUKAN kode valid untuk membuat dan mendekonstruksi objek Fish?

val (name1, species1, colors1) = Fish("Pat", "Plecostomus", "gold")

val (name2, _, colors2) = Fish("Bitey", "shark", "gray")

val (name3, species3, _) = Fish("Amy", "angelfish", "blue and black stripes")

val (name4, species4, colors4) = Fish("Harry", "halibut")

Pertanyaan 5

Misalkan Anda memiliki kebun binatang dengan banyak hewan yang perlu dirawat. Manakah dari pilihan berikut yang BUKAN merupakan bagian dari penerapan pengawasan?

interface untuk berbagai jenis makanan yang dimakan hewan.

▢ Class abstract Caretaker yang dapat Anda gunakan untuk membuat berbagai jenis pengasuh.

interface untuk memberikan air bersih kepada hewan.

▢ Class data untuk entri dalam jadwal pemberian makan.

Lanjutkan ke pelajaran berikutnya: 5.1 Ekstensi

Untuk ringkasan kursus, termasuk link ke codelab lainnya, lihat "Kotlin Bootcamp for Programmers: Welcome to the course".