Mengonversi ke Kotlin

Dalam Codelab ini, Anda akan mempelajari cara mengonversi kode dari Java ke Kotlin. Anda juga akan mempelajari apa itu konvensi bahasa Kotlin dan cara memastikan bahwa kode yang ditulis mengikutinya.

Codelab ini cocok untuk semua developer yang menggunakan Java dan mempertimbangkan untuk memigrasikan project mereka ke Kotlin. Kita akan memulai tutorial ini dengan beberapa class Java yang akan dikonversi ke Kotlin dengan menggunakan IDE. Kemudian, kita akan melihat kode yang dikonversi dan juga melihat bagaimana kode tersebut dapat ditingkatkan dengan membuatnya lebih idiomatis dan menghindari kesalahan umum.

Yang akan Anda pelajari

Anda akan mempelajari cara mengonversi Java ke Kotlin. Untuk melakukannya, Anda akan mempelajari fitur dan konsep bahasa Kotlin berikut:

  • Menangani nullability
  • Mengimplementasikan singleton
  • Class data
  • Menangani string
  • Operator Elvis
  • Destrukturisasi
  • Properti dan properti pendukung
  • Argumen default dan parameter bernama
  • Menangani koleksi
  • Fungsi ekstensi
  • Fungsi dan parameter level atas
  • Kata kunci let, apply, with, dan run

Asumsi

Anda seharusnya sudah paham tentang Java.

Yang akan Anda butuhkan

Membuat project baru

Jika Anda menggunakan InteliJ IDEA, buat project Java baru dengan Kotlin/JVM.

Jika Anda menggunakan Android Studio, buat project baru tanpa Aktivitas. SDK minimum dapat berupa nilai apa saja, ini tidak akan memengaruhi hasil.

Kode

Kita akan membuat objek model User dan class singleton Repository yang berfungsi dengan objek User dan mengekspos daftar pengguna serta nama pengguna yang telah diformat.

Buat file baru bernama User.java pada app/java/<yourpackagename> dan tempelkan kode berikut:

public class User {

    @Nullable
    private String firstName;
    @Nullable
    private String lastName;

    public User(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

}

Anda akan melihat IDE yang memberi tahu bahwa @Nullable belum ditentukan. Jadi, impor androidx.annotation.Nullable jika Anda menggunakan Android Studio, atau org.jetbrains.annotations.Nullable jika menggunakan IntelliJ.

Buat file baru bernama Repository.java dan tempelkan kode berikut:

import java.util.ArrayList;
import java.util.List;

public class Repository {

    private static Repository INSTANCE = null;

    private List<User> users = null;

    public static Repository getInstance() {
        if (INSTANCE == null) {
            synchronized (Repository.class) {
                if (INSTANCE == null) {
                    INSTANCE = new Repository();
                }
            }
        }
        return INSTANCE;
    }

    // keeping the constructor private to enforce the usage of getInstance
    private Repository() {

        User user1 = new User("Jane", "");
        User user2 = new User("John", null);
        User user3 = new User("Anne", "Doe");

        users = new ArrayList();
        users.add(user1);
        users.add(user2);
        users.add(user3);
    }

    public List<User> getUsers() {
        return users;
    }

    public List<String> getFormattedUserNames() {
        List<String> userNames = new ArrayList<>(users.size());
        for (User user : users) {
            String name;

            if (user.getLastName() != null) {
                if (user.getFirstName() != null) {
                    name = user.getFirstName() + " " + user.getLastName();
                } else {
                    name = user.getLastName();
                }
            } else if (user.getFirstName() != null) {
                name = user.getFirstName();
            } else {
                name = "Unknown";
            }
            userNames.add(name);
        }
        return userNames;
    }
}

IDE dapat berfungsi dengan baik dalam mengonversi kode Java menjadi kode Kotlin secara otomatis, tetapi kadang perlu sedikit bantuan. Mari kita buat IDE melakukan langkah awal saat konversi. Setelah itu, kita akan membahas kode yang dihasilkan untuk memahami bagaimana dan mengapa kode tersebut dikonversi dengan cara ini.

Buka file User.java dan konversikan ke Kotlin: Menu bar -> Code -> Convert Java File to Kotlin File.

Jika IDE meminta untuk memperbaiki setelah konversi, tekan Yes.

25e57db5b8e76557.png

Anda akan melihat kode Kotlin berikut:

class User(var firstName: String?, var lastName: String?)

Perhatikan bahwa User.java diganti namanya menjadi User.kt. File Kotlin memiliki ekstensi .kt.

Di class User Java, kita memiliki dua properti: firstName dan lastName. Setiap properti memiliki metode pengambil dan penyetel, sehingga nilainya dapat berubah. Kata kunci Kotlin untuk variabel yang dapat berubah adalah var, sehingga pengonversi menggunakan var untuk setiap properti ini. Jika properti Java hanya memiliki pengambil, properti tersebut tidak akan dapat berubah dan akan dideklarasikan sebagai variabel val. val mirip dengan kata kunci final di Java.

Salah satu perbedaan utama antara Kotlin dan Java adalah Kotlin secara eksplisit menentukan apakah variabel dapat menerima nilai null. Ini dilakukan dengan menambahkan ? ke deklarasi jenis.

Karena kita telah menandai firstName dan lastName sebagai nullable, pengonversi secara otomatis akan menandai properti sebagai nullable dengan String?. Jika Anda memberi anotasi pada anggota Java sebagai non-null (menggunakan org.jetbrains.annotations.NotNull atau androidx.annotation.NonNull), pengonversi akan mengenalinya dan membuat kolom non-null di Kotlin.

Konversi dasar sudah selesai. Namun, kita dapat menuliskannya dengan cara yang lebih idiomatis. Mari kita lihat caranya.

Class data

Class User hanya menyimpan data. Kotlin memiliki kata kunci class dengan peran ini: data. Dengan menandai class ini sebagai class data, compiler akan otomatis membuat pengambil dan penyetel untuk kita. Tindakan ini juga akan mendapatkan fungsi equals(), hashCode(), dan toString().

Mari kita tambahkan kata kunci data ke class User:

data class User(var firstName: String, var lastName: String)

Kotlin, seperti halnya Java, dapat memiliki konstruktor utama dan satu atau beberapa konstruktor sekunder. Konstruktor dalam contoh di atas adalah konstruktor utama class User. Jika Anda mengonversi class Java yang memiliki beberapa konstruktor, pengonversi juga akan otomatis membuat beberapa konstruktor di Kotlin. Konstruktor tersebut ditentukan dengan menggunakan kata kunci constructor.

Jika ingin membuat instance class ini, kita dapat melakukannya seperti ini:

val user1 = User("Jane", "Doe")

Persamaan

Kotlin memiliki dua jenis persamaan:

  • Persamaan struktural menggunakan operator == dan memanggil equals() untuk menentukan apakah dua instance tersebut sama.
  • Persamaan referensial menggunakan operator === dan memeriksa apakah dua referensi akan mengarah ke objek yang sama.

Properti yang ditentukan dalam konstruktor utama class data akan digunakan untuk pemeriksaan persamaan struktural.

val user1 = User("Jane", "Doe")
val user2 = User("Jane", "Doe")
val structurallyEqual = user1 == user2 // true
val referentiallyEqual = user1 === user2 // false

Di Kotlin, kita bisa menetapkan nilai default ke argumen dalam panggilan fungsi. Nilai default digunakan saat argumen dihilangkan. Di Kotlin, konstruktor juga merupakan fungsi, sehingga kita dapat menggunakan argumen default untuk menentukan bahwa nilai default lastName adalah null. Untuk melakukannya, kita cukup menetapkan null ke lastName.

data class User(var firstName: String?, var lastName: String? = null)

// usage
val jane = User("Jane") // same as User("Jane", null)
val joe = User("Joe", "Doe")

Kotlin memungkinkan Anda memberi label pada argumen saat fungsi dipanggil:

val john = User(firstName = "John", lastName = "Doe")

Sebagai kasus penggunaan yang berbeda, misalnya firstName memiliki null sebagai nilai defaultnya dan lastName tidak. Dalam hal ini, karena parameter default akan mendahului parameter tanpa nilai default, Anda harus memanggil fungsi dengan argumen bernama:

data class User(var firstName: String? = null, var lastName: String?)

// usage
val jane = User(lastName = "Doe") // same as User(null, "Doe")
val john = User("John", "Doe")

Nilai default adalah konsep yang penting dan sering digunakan dalam kode Kotlin. Dalam codelab ini, kita ingin selalu menentukan nama depan dan belakang dalam deklarasi objek User, sehingga kita tidak perlu nilai default.

Sebelum melanjutkan codelab ini, pastikan class User Anda adalah class data. Sekarang, mari kita mengonversi class Repository ke Kotlin. Hasil konversi otomatis akan terlihat seperti ini:

import java.util.*

class Repository private constructor() {
    private var users: MutableList<User?>? = null
    fun getUsers(): List<User?>? {
        return users
    }

    val formattedUserNames: List<String?>
        get() {
            val userNames: MutableList<String?> =
                ArrayList(users!!.size)
            for (user in users) {
                var name: String
                name = if (user!!.lastName != null) {
                    if (user!!.firstName != null) {
                        user!!.firstName + " " + user!!.lastName
                    } else {
                        user!!.lastName
                    }
                } else if (user!!.firstName != null) {
                    user!!.firstName
                } else {
                    "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

    companion object {
        private var INSTANCE: Repository? = null
        val instance: Repository?
            get() {
                if (INSTANCE == null) {
                    synchronized(Repository::class.java) {
                        if (INSTANCE == null) {
                            INSTANCE =
                                Repository()
                        }
                    }
                }
                return INSTANCE
            }
    }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Mari lihat apa yang dilakukan pengonversi otomatis:

  • Daftar users adalah nullable karena objek tidak dibuat pada waktu deklarasi
  • Fungsi di Kotlin seperti getUsers() dideklarasikan dengan pengubah fun
  • Metode getFormattedUserNames() kini menjadi properti yang disebut formattedUserNames
  • Iterasi melalui daftar pengguna (yang awalnya merupakan bagian dari getFormattedUserNames() ) memiliki sintaksis yang berbeda dengan yang Java
  • Kolom static kini menjadi bagian dari blok companion object
  • Blok init telah ditambahkan

Sebelum dilanjutkan, mari kita bersihkan sedikit kodenya. Jika melihat di konstruktor, kita akan melihat pengonversi membuat daftar users sebagai daftar yang dapat diubah yang menyimpan objek nullable. Meskipun daftar bisa berupa null, anggaplah daftar tersebut tidak dapat menampung pengguna null. Jadi, mari kita lakukan hal berikut:

  • Hapus ? di User? dalam deklarasi jenis users
  • Hapus ? di User? untuk jenis nilai getUsers() yang ditampilkan sehingga menampilkan List<User>?

Pemblokiran init

Di Kotlin, konstruktor utama tidak dapat berisi kode apa pun sehingga kode inisialisasi ditempatkan dalam blok init. Fungsinya sama.

class Repository private constructor() {
    ...
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Sebagian besar kode init menangani inisialisasi properti. Hal ini juga dapat dilakukan di deklarasi properti. Misalnya, di versi Kotlin class Repository, kita melihat properti pengguna diinisialisasi di deklarasi.

private var users: MutableList<User>? = null

Properti dan metode static Kotlin

Di Java, kita menggunakan kata kunci static untuk kolom atau fungsi yang mengatakan bahwa kolom atau fungsi tersebut termasuk dalam suatu class, tetapi bukan instance dari class tersebut. Inilah alasan kita membuat kolom statis INSTANCE dalam class Repository. Persamaan Kotlin untuk ini adalah blok companion object. Di sini, Anda juga akan mendeklarasikan kolom statis dan fungsi statis. Pengonversi membuat blok objek pendamping dan memindahkan kolom INSTANCE di sini.

Menangani singleton

Karena hanya memerlukan satu instance dari class Repository, kita menggunakan pola singleton di Java. Dengan Kotlin, Anda dapat menerapkan pola ini di level compiler dengan mengganti kata kunci class dengan object.

Hapus konstruktor pribadi dan ganti definisi class dengan object Repository. Hapus juga objek pendamping.

object Repository {

    private var users: MutableList<User>? = null
    fun getUsers(): List<User>? {
       return users
    }

    val formattedUserNames: List<String>
        get() {
            val userNames: MutableList<String> =
                ArrayList(users!!.size)
        for (user in users) {
            var name: String
            name = if (user!!.lastName != null) {
                if (user!!.firstName != null) {
                    user!!.firstName + " " + user!!.lastName
                } else {
                    user!!.lastName
                }
            } else if (user!!.firstName != null) {
                user!!.firstName
            } else {
                "Unknown"
            }
            userNames.add(name)
       }
       return userNames
   }

    // keeping the constructor private to enforce the usage of getInstance
    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")
        users = ArrayList<Any?>()
        users.add(user1)
        users.add(user2)
        users.add(user3)
    }
}

Saat menggunakan class object, kita cukup memanggil fungsi dan properti langsung pada objek, seperti ini:

val formattedUserNames = Repository.formattedUserNames

Perhatikan bahwa jika properti tidak memiliki pengubah visibilitas, properti tersebut bersifat publik secara default, seperti pada kasus properti formattedUserNames dalam objek Repository.

Saat mengonversi class Repository ke Kotlin, pengonversi otomatis membuat daftar pengguna nullable, karena tidak diinisialisasi ke objek saat dideklarasikan. Akibatnya, untuk semua penggunaan objek users, operator pernyataan not-null !! perlu digunakan. (Anda akan melihat users!! dan user!! di seluruh kode yang telah dikonversi.) Operator !! mengonversi variabel apa pun menjadi jenis non-null, sehingga Anda dapat mengakses properti atau memanggil fungsi di dalamnya. Namun, pengecualian akan ditampilkan jika nilai variabel memang null. Dengan menggunakan !!, kemungkinan besar pengecualian ditampilkan saat runtime.

Sebagai gantinya, lebih baik menangani nullability dengan menggunakan salah satu metode berikut:

  • Melakukan pemeriksaan null ( if (users != null) {...} )
  • Menggunakan operator elvis ?: (dibahas nanti di codelab)
  • Menggunakan beberapa fungsi standar Kotlin (dibahas nanti di codelab)

Dalam kasus ini, kita tahu bahwa daftar pengguna tidak harus menjadi nullable, karena daftar tersebut diinisialisasi langsung setelah objek dikonstruksi (di blok init). Dengan begitu, kita dapat langsung membuat instance objek users saat mendeklarasikannya.

Saat membuat instance jenis koleksi, Kotlin menyediakan beberapa fungsi bantuan untuk membuat kode lebih mudah dibaca dan fleksibel. Di sini kita menggunakan MutableList untuk users:

private var users: MutableList<User>? = null

Untuk memudahkan, kita dapat menggunakan fungsi mutableListOf() dan memberikan jenis elemen daftar. mutableListOf<User>() membuat daftar kosong yang dapat menyimpan objek User. Karena jenis data variabel kini dapat ditentukan oleh compiler, hapus deklarasi jenis eksplisit dari properti users.

private val users = mutableListOf<User>()

Kita juga mengubah var menjadi val karena pengguna akan mengisi referensi yang tidak dapat diubah ke daftar pengguna. Perhatikan bahwa referensi tidak dapat diubah, tetapi daftar itu sendiri dapat diubah (Anda dapat menambahkan atau menghapus elemen).

Karena variabel users sudah diinisialisasi, hapus inisialisasi ini dari blok init:

users = ArrayList<Any?>()

Kemudian, blok init harus terlihat seperti ini:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    users.add(user1)
    users.add(user2)
    users.add(user3)
}

Dengan perubahan tersebut, properti users kini adalah non-null, dan kita dapat menghapus semua kemunculan operator !! yang tidak perlu. Perhatikan bahwa Anda akan tetap melihat error kompilasi di Android Studio, tetapi lanjutkan dengan beberapa langkah codelab berikutnya untuk menyelesaikannya.

val userNames: MutableList<String?> = ArrayList(users.size)
for (user in users) {
    var name: String
    name = if (user.lastName != null) {
        if (user.firstName != null) {
            user.firstName + " " + user.lastName
        } else {
            user.lastName
        }
    } else if (user.firstName != null) {
        user.firstName
    } else {
        "Unknown"
    }
    userNames.add(name)
}

Selain itu, untuk nilai userNames, jika Anda menentukan jenis ArrayList sebagai holding Strings, Anda dapat menghapus jenis eksplisit dalam deklarasi karena akan disimpulkan.

val userNames = ArrayList<String>(users.size)

Destrukturisasi

Dengan Kotlin, objek dapat didestrukturisasi ke dalam sejumlah variabel menggunakan sintaksis yang disebut deklarasi destrukturisasi. Beberapa variabel telah dibuat dan dapat digunakan secara terpisah.

Misalnya, class data mendukung destrukturisasi sehingga kita dapat mendestrukturisasi objek User di loop for menjadi (firstName, lastName). Hal ini memungkinkan kita bekerja secara langsung dengan nilai firstName dan lastName. Perbarui loop for seperti yang ditunjukkan di bawah. Ganti semua instance user.firstName dengan firstName dan ganti user.lastName dengan lastName.

for ((firstName, lastName) in users) {
    var name: String
    name = if (lastName != null) {
        if (firstName != null) {
            firstName + " " + lastName
        } else {
            lastName
        }
    } else if (firstName != null) {
        firstName
    } else {
        "Unknown"
    }
    userNames.add(name)
}

Ekspresi if

Nama dalam daftar userNames belum berupa format yang kita inginkan. Karena lastName dan firstName dapat berupa null, kita harus menangani nullability saat membuat daftar nama pengguna yang telah diformat. Kita ingin menampilkan "Unknown" jika salah satu nama tidak ada. Karena variabel name tidak akan diubah setelah ditetapkan satu kali, kita dapat menggunakan val bukan var. Buat perubahan ini terlebih dahulu.

val name: String

Lihat kode yang menetapkan variabel nama. Ini mungkin hal baru bagi Anda untuk melihat variabel disetel menjadi sama dan blok kode if/else. Tindakan ini diizinkan karena di Kotlin, if, when, for, dan while adalah ekspresi dan akan menampilkan nilai. Baris terakhir pernyataan if akan ditetapkan ke name. Satu-satunya tujuan pemblokiran ini adalah menginisialisasi nilai name.

Pada dasarnya, logika yang ditampilkan di sini adalah jika lastName adalah null, name akan disetel ke firstName atau "Unknown".

name = if (lastName != null) {
    if (firstName != null) {
        firstName + " " + lastName
    } else {
        lastName
    }
} else if (firstName != null) {
    firstName
} else {
    "Unknown"
}

Operator Elvis

Kode ini dapat ditulis dengan cara yang lebih idiomatis menggunakan operator elvis ?:. Operator elvis akan menampilkan ekspresi di sisi kiri jika bukan null, atau ekspresi di sisi kanan, jika sisi kiri adalah null.

Jadi, dalam kode berikut, firstName akan ditampilkan jika bukan null. Jika firstName adalah null, ekspresi ini akan menampilkan nilai di sebelah kanan , "Unknown":

name = if (lastName != null) {
    ...
} else {
    firstName ?: "Unknown"
}

Kotlin mempermudah penggunaan String dengan template String. Template string memungkinkan Anda mereferensikan variabel di dalam deklarasi string dengan simbol $ sebelum variabel. Anda juga dapat menempatkan ekspresi di dalam deklarasi string, dengan menempatkan ekspresi dalam { } dan menggunakan simbol $ sebelumnya. Contoh: ${user.firstName}.

Saat ini kode Anda menggunakan penyambungan string untuk menggabungkan firstName dan lastName ke dalam nama pengguna.

if (firstName != null) {
    firstName + " " + lastName
}

Sebagai gantinya, ganti penyambungan String dengan:

if (firstName != null) {
    "$firstName $lastName"
}

Penggunaan template string dapat menyederhanakan kode Anda.

IDE Anda akan menampilkan peringatan jika ada cara lain yang lebih Idiomatis untuk menulis kode. Anda akan melihat garis bawah yang berlekuk-lekuk dalam kode, dan saat mengarahkan kursor ke kode, Anda akan melihat saran cara memfaktorkan ulang kode.

Saat ini, Anda seharusnya melihat peringatan yang menyebutkan bahwa deklarasi name dapat digabungkan dengan tugas. Mari kita terapkan. Karena jenis variabel name dapat disimpulkan, kita dapat menghapus deklarasi jenis String eksplisit. Sekarang formattedUserNames terlihat seperti ini:

val formattedUserNames: List<String?>
    get() {
        val userNames = ArrayList<String>(users.size)
        for ((firstName, lastName) in users) {
            val name = if (lastName != null) {
                if (firstName != null) {
                    "$firstName $lastName"
                } else {
                    lastName
                }
            } else {
                firstName ?: "Unknown"
            }
            userNames.add(name)
        }
        return userNames
    }

Kita dapat membuat satu penyesuaian tambahan. Logika UI akan menampilkan "Unknown" jika nama depan dan belakang tidak ada. Dengan kata lain, objek null tidak didukung. Dengan begitu, untuk jenis data formattedUserNames, ganti List<String?> dengan List<String>.

val formattedUserNames: List<String>

Mari kita pelajari tentang pengambil formattedUserNames lebih lanjut dan melihat cara membuatnya lebih idiomatis. Saat ini kode melakukan hal berikut:

  • Membuat daftar string baru
  • Melakukan iterasi melalui daftar pengguna
  • Membuat nama yang diformat untuk setiap pengguna, berdasarkan nama depan dan nama belakang pengguna
  • Menampilkan daftar yang baru dibuat
    val formattedUserNames: List<String>
        get() {
            val userNames = ArrayList<String>(users.size)
            for ((firstName, lastName) in users) {
                val name = if (lastName != null) {
                    if (firstName != null) {
                        "$firstName $lastName"
                    } else {
                        lastName
                    }
                } else {
                    firstName ?: "Unknown"
                }
                userNames.add(name)
            }
            return userNames
        }

Kotlin menyediakan daftar lengkap transformasi koleksi sehingga pengembangan dapat dibuat lebih cepat dan lebih aman dengan memperluas kemampuan API Java Collections. Salah satunya adalah fungsi map. Fungsi ini akan menampilkan daftar baru yang berisi hasil penerapan fungsi transformasi yang diberikan ke setiap elemen yang ada dalam daftar asli. Jadi, sebagai ganti membuat daftar baru dan iterasi melalui daftar pengguna secara manual, kita dapat menggunakan fungsi map dan memindahkan logika yang sudah ada di loop for ke dalam isi map. Secara default, nama item daftar saat ini yang digunakan di map adalah it, tetapi Anda dapat mengganti it dengan nama variabel Anda sendiri agar mudah dibaca. Dalam kasus ini, mari kita beri nama user:

val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                val name = if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
                name
            }
        }

Perhatikan bahwa kita menggunakan operator Elvis untuk menampilkan "Unknown" jika user.lastName adalah null, karena user.lastName adalah jenis String? dan String diperlukan untuk name.

...
else {
    user.lastName ?: "Unknown"
}
...

Untuk lebih menyederhanakannya, kita dapat menghapus variabel name:

val formattedUserNames: List<String>
        get() {
            return users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

Kita melihat bahwa pengonversi otomatis menggantikan fungsi getFormattedUserNames() dengan properti yang disebut formattedUserNames yang memiliki pengambil khusus. Di balik layar, Kotlin masih membuat metode getFormattedUserNames() yang menampilkan List.

Di Java, kita akan mengekspos properti class melalui fungsi pengambil dan penyetel. Kotlin memungkinkan Anda memiliki diferensiasi yang lebih baik antara properti class, yang diekspresikan dengan kolom, dan fungsionalitas, yaitu tindakan yang dapat dilakukan class, yang diekspresikan dengan fungsi. Dalam hal ini, class Repository sangat sederhana dan tidak melakukan tindakan apa pun sehingga hanya memiliki kolom.

Logika yang dipicu dalam fungsi getFormattedUserNames() Java kini akan dipicu saat memanggil pengambil properti Kotlin formattedUserNames.

Meskipun kita tidak secara eksplisit memiliki kolom yang sesuai dengan properti formattedUserNames, Kotlin menyediakan kolom pendukung otomatis bernama field yang dapat diakses dari pembuat dan penyetel kustom, jika diperlukan.

Namun, terkadang kita menginginkan beberapa fungsi tambahan yang tidak disediakan oleh kolom pendukung otomatis.

Mari lihat contoh yang ada.

Dalam class Repository, kita memiliki daftar pengguna yang dapat berubah dan diekspos dalam fungsi getUsers() yang dihasilkan dari kode Java:

fun getUsers(): List<User>? {
    return users
}

Masalah yang ada di sini adalah dengan menampilkan users, konsumen class Repositori dapat mengubah daftar pengguna kita, dan itu bukanlah ide yang bagus. Mari kita perbaiki menggunakan properti pendukung.

Pertama, ganti nama users menjadi _users. Tandai nama variabel, klik kanan untuk melakukan Refactor > Rename variabel. Kemudian, tambahkan properti publik yang tidak dapat diubah yang menampilkan daftar pengguna. Mari kita menyebutnya users:

private val _users = mutableListOf<User>()
val users: List<User>
    get() = _users

Pada tahap ini, Anda dapat menghapus metode getUsers().

Dengan perubahan di atas, properti _users pribadi akan menjadi properti pendukung untuk properti users publik. Di luar class Repository, daftar _users tidak dapat diubah, karena konsumen class hanya dapat mengakses daftar melalui users.

Kode lengkap:

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user ->
                if (user.lastName != null) {
                    if (user.firstName != null) {
                        "${user.firstName} ${user.lastName}"
                    } else {
                        user.lastName ?: "Unknown"
                    }
                }  else {
                    user.firstName ?: "Unknown"
                }
            }
        }

    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Saat ini class Repository tahu cara menghitung nama pengguna yang diformat untuk objek User. Namun, jika ingin menggunakan kembali logika format yang sama di class lain, kita harus menyalin dan menempelkan logika tersebut atau memindahkannya ke class User.

Kotlin menyediakan kemampuan untuk mendeklarasikan fungsi dan properti di luar class, objek, atau antarmuka. Misalnya, fungsi mutableListOf() yang kita gunakan untuk membuat instance List baru sudah ditentukan di Collections.kt dari Kotlin Standard Library.

Di Java, kapan pun Anda membutuhkan beberapa fungsi utilitas, Anda kemungkinan besar akan membuat class Util dan mendeklarasikan fungsi tersebut sebagai fungsi statis. Di Kotlin, Anda dapat mendeklarasikan fungsi level atas tanpa harus memiliki class. Namun, Kotlin juga menyediakan kemampuan untuk membuat fungsi ekstensi. Fungsi ekstensi adalah fungsi yang memperluas jenis tertentu, tetapi dideklarasikan di luar tipe.

Visibilitas fungsi dan properti ekstensi dapat dibatasi dengan menggunakan pengubah visibilitas. Cara ini akan membatasi penggunaan hanya untuk class yang memerlukan ekstensi, dan tidak mencemari namespace.

Untuk class User, kita dapat menambahkan fungsi ekstensi yang menghitung nama yang diformat, atau kita dapat menyimpan nama yang diformat di properti ekstensi. Ini dapat ditambahkan di luar class Repository, dalam file yang sama:

// extension function
fun User.getFormattedName(): String {
    return if (lastName != null) {
        if (firstName != null) {
            "$firstName $lastName"
        } else {
            lastName ?: "Unknown"
        }
    } else {
        firstName ?: "Unknown"
    }
}

// extension property
val User.userFormattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

// usage:
val user = User(...)
val name = user.getFormattedName()
val formattedName = user.userFormattedName

Kemudian, kita dapat menggunakan fungsi dan properti ekstensi seolah-olah keduanya adalah bagian dari class User.

Karena nama yang diformat adalah properti class User dan bukan fungsi class Repository, mari kita gunakan properti ekstensi. Sekarang file Repository terlihat seperti ini:

val User.formattedName: String
    get() {
        return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf<User>()
    val users: List<User>
      get() = _users

    val formattedUserNames: List<String>
        get() {
            return _users.map { user -> user.formattedName }
        }

    init {
        val user1 = User("Jane", "")
        val user2 = User("John", null)
        val user3 = User("Anne", "Doe")

        _users.add(user1)
        _users.add(user2)
        _users.add(user3)
    }
}

Kotlin Standard Library menggunakan fungsi ekstensi untuk memperluas fungsi beberapa API Java; banyak fungsi di Iterable dan Collection diimplementasikan sebagai fungsi ekstensi. Misalnya, fungsi map yang kita gunakan pada langkah sebelumnya adalah fungsi ekstensi di Iterable.

Dalam kode class Repository, kita menambahkan beberapa objek User ke daftar _users. Panggilan ini dapat dibuat lebih unik dengan bantuan fungsi cakupan Kotlin.

Untuk menjalankan kode hanya dalam konteks objek tertentu, tanpa perlu mengakses objek berdasarkan namanya, Kotlin menawarkan 5 fungsi cakupan: let, apply, with, run, dan also. Fungsi ini membuat kode Anda lebih mudah dibaca dan lebih ringkas. Semua fungsi cakupan memiliki penerima (this), dapat juga memiliki argumen (it), dan dapat menampilkan nilai.

Berikut tips praktis yang dapat membantu Anda mengingat kapan menggunakan setiap fungsi:

6b9283d411fb6e7b.png

Karena mengonfigurasikan objek _users di Repository, kita dapat membuat kode yang lebih idiomatis dengan menggunakan fungsi apply:

init {
    val user1 = User("Jane", "")
    val user2 = User("John", null)
    val user3 = User("Anne", "Doe")

    _users.apply {
       // this == _users
       add(user1)
       add(user2)
       add(user3)
    }
 }

Dalam Codelab ini, kita telah membahas dasar-dasar yang diperlukan untuk mulai mengonversi kode dari Java ke Kotlin. Konversi ini tidak bergantung pada platform pengembangan dan membantu memastikan bahwa kode yang Anda tulis adalah Kotlin idiomatis.

Kotlin Idiomatis membuat penulisan kode menjadi singkat dan menarik. Dengan semua fitur yang disediakan oleh Kotlin, ada begitu banyak cara untuk membuat kode Anda menjadi lebih aman, lebih ringkas, dan lebih mudah dibaca. Misalnya, kita bahkan dapat mengoptimalkan class Repository dengan membuat instance daftar _users dengan pengguna secara langsung dalam deklarasi, sehingga blok init dapat dihilangkan:

private val users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))

Kita telah membahas beragam topik, dari menangani nullability, singleton, String, dan koleksi hingga topik seperti fungsi ekstensi, fungsi tingkat atas, properti, dan fungsi cakupan. Kita sudah beralih dari dua class Java ke dua class Kotlin yang sekarang terlihat seperti ini:

User.kt

data class User(var firstName: String?, var lastName: String?)

Repository.kt

val User.formattedName: String
    get() {
       return if (lastName != null) {
            if (firstName != null) {
                "$firstName $lastName"
            } else {
                lastName ?: "Unknown"
            }
        } else {
            firstName ?: "Unknown"
        }
    }

object Repository {

    private val _users = mutableListOf(User("Jane", ""), User("John", null), User("Anne", "Doe"))
    val users: List<User>
        get() = _users

    val formattedUserNames: List<String>
        get() = _users.map { user -> user.formattedName }
}

Berikut adalah TL;DR dari fungsi Java beserta pemetaannya ke Kotlin:

Java

Kotlin

Objek final

Objek val

equals()

==

==

===

Class yang hanya menyimpan data

Class data

Inisialisasi dalam konstruktor

Inisialisasi dalam blok init

Kolom dan fungsi static

kolom dan fungsi yang dideklarasikan dalam companion object

Class singleton

object

Untuk mencari tahu lebih lanjut tentang Kotlin dan juga cara menggunakannya di platform Anda, lihat referensi berikut: