Dokumen ini berisi sekumpulan aturan untuk menulis API publik dalam Java dan Kotlin agar nantinya kode terasa idiomatis jika dipakai dari bahasa lain.
Terakhir diperbarui: 18-05-2018
Java (untuk pemakaian Kotlin)
Tidak ada kata kunci keras
Jangan gunakan kata kunci keras Kotlin sebagai nama metode atau kolom. Jika digunakan, perlu tanda kutip terbalik untuk keluar saat memanggil dari Kotlin. Kata kunci yang mudah, kata kunci pengubah, dan ID khusus diizinkan.
Misalnya, fungsi when
Mockito memerlukan tanda kutip terbalik jika digunakan dari Kotlin:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Menghindari nama ekstensi Any
Hindari penggunaan nama fungsi ekstensi pada Any
untuk metode atau nama properti ekstensi pada Any
untuk kolom kecuali jika benar-benar diperlukan. Meskipun metode dan kolom anggota akan selalu diprioritaskan daripada fungsi atau properti ekstensi Any
, mungkin akan sulit saat membaca kode untuk mengetahui metode dan kolom mana yang dipanggil.
Anotasi nullability
Setiap jenis kolom, nilai yang ditampilkan, dan parameter bukan primitif pada API publik harus memiliki anotasi nullability. Jenis yang tidak dianotasi diinterpretasikan sebagai jenis "platform", yang memiliki nullability ambigu.
Secara default, tanda compiler Kotlin mematuhi anotasi JSR 305, tetapi menandainya dengan peringatan. Anda juga dapat menetapkan tanda agar compiler memperlakukan anotasi sebagai error.
Parameter lambda akhir
Jenis parameter yang memenuhi syarat untuk konversi SAM harus menjadi yang terakhir.
Misalnya, tanda tangan metode RxJava 2’s Flowable.create()
ditentukan sebagai:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Karena FlowableOnSubscribe memenuhi syarat untuk konversi SAM, panggilan fungsi metode ini dari Kotlin akan terlihat seperti ini:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Namun, jika parameter dibalik dalam tanda tangan metode, pemanggilan fungsi dapat menggunakan sintaks lambda di akhir:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Awalan properti
Untuk metode yang direpresentasikan sebagai properti di Kotlin, awalan bergaya “bean” yang ketat harus digunakan.
Metode pengakses memerlukan awalan ‘get’ atau untuk metode yang menampilkan boolean, awalan ‘is’ dapat digunakan.
public final class User { public String getName() { /* … */ } public boolean isActive() { /* … */ } }
val name = user.name // Invokes user.getName() val active = user.isActive // Invokes user.isActive()
Metode mutator yang terkait memerlukan awalan ‘set’.
public final class User { public String getName() { /* … */ } public void setName(String name) { /* … */ } public boolean isActive() { /* … */ } public void setActive(boolean active) { /* … */ } }
user.name = "Bob" // Invokes user.setName(String) user.isActive = true // Invokes user.setActive(boolean)
Jika Anda ingin metode diekspos sebagai properti, jangan gunakan awalan tidak standar seperti ‘has’/’set’ atau pengakses berawalan bukan ‘get’. Metode dengan awalan tidak standar masih dapat dipanggil sebagai fungsi yang mungkin dapat diterima, bergantung pada perilaku metode.
Overload operator
Perhatikan nama metode yang memungkinkan sintaksis situs panggilan khusus (misalnya, overload operator) dalam Kotlin. Pastikan nama metode seperti itu logis digunakan dengan sintaksis yang dipersingkat.
public final class IntBox { private final int value; public IntBox(int value) { this.value = value; } public IntBox plus(IntBox other) { return new IntBox(value + other.value); } }
val one = IntBox(1) val two = IntBox(2) val three = one + two // Invokes one.plus(two)
Kotlin (untuk pemakaian Java)
Nama file
Jika file berisi fungsi atau properti tingkat atas, selalu anotasikan dengan @file:JvmName("Foo")
untuk memberikan nama yang bagus.
Secara default, anggota tingkat atas dalam file MyClass.kt akan berakhir di class bernama MyClassKt
yang tidak menarik dan membocorkan bahasa sebagai detail implementasi.
Sebaiknya tambahkan @file:JvmMultifileClass
untuk menggabungkan anggota tingkat atas dari beberapa file ke dalam satu class.
Argumen lambda
Jenis fungsi yang dimaksudkan untuk digunakan dari Java harus menghindari Unit
sebagai jenis nilai yang ditampilkan. Hal ini memerlukan pernyataan return
Unit.INSTANCE;
eksplisit yang tidak idiomatis.
fun sayHi(callback: (String) -> Unit) = /* … */
// Kotlin caller: greeter.sayHi { Log.d("Greeting", "Hello, $it!") }
// Java caller: greeter.sayHi(name -> { Log.d("Greeting", "Hello, " + name + "!"); return Unit.INSTANCE; });
Sintaksis ini juga tidak memungkinkan untuk menyediakan jenis yang dinamai secara semantik sehingga dapat diterapkan pada jenis lain.
Menentukan antarmuka metode abstrak tunggal (SAM) yang telah diberi nama di Kotlin untuk jenis lambda akan memperbaiki masalah pada Java, tetapi mencegah sintaksis lambda digunakan di Kotlin.
interface GreeterCallback { fun greetName(name: String): Unit } fun sayHi(callback: GreeterCallback) = /* … */
// Kotlin caller: greeter.sayHi(object : GreeterCallback { override fun greetName(name: String) { Log.d("Greeting", "Hello, $name!") } })
// Java caller: greeter.sayHi(name -> Log.d("Greeting", "Hello, " + name + "!"))
Menentukan antarmuka SAM yang telah diberi nama di Java memungkinkan penggunaan versi yang sedikit lebih rendah dari sintaksis lambda Kotlin, tempat jenis antarmuka harus ditentukan secara eksplisit.
// Defined in Java: interface GreeterCallback { void greetName(String name); }
fun sayHi(greeter: GreeterCallback) = /* … */
// Kotlin caller: greeter.sayHi(GreeterCallback { Log.d("Greeting", "Hello, $it!") })
// Java caller: greeter.sayHi(name -> Log.d("Greeter", "Hello, " + name + "!"));
Saat ini tidak ada cara untuk menentukan jenis parameter untuk digunakan sebagai lambda dari Java dan Kotlin sehingga terasa idiomatis dari kedua bahasa tersebut. Rekomendasi saat ini adalah memilih jenis fungsi meskipun mengalami penurunan kualitas pengalaman dari Java ketika jenis nilai yang ditampilkan adalah Unit
.
Hindari generik Nothing
Jenis yang parameter generiknya adalah Nothing
akan diekspos sebagai jenis raw ke Java. Jenis raw jarang digunakan di Java dan harus dihindari.
Pengecualian dokumen
Fungsi yang dapat memunculkan pengecualian yang diperiksa harus mendokumentasikannya dengan @Throws
. Pengecualian waktu proses harus didokumentasikan di KDoc.
Perhatikan API yang didelegasikan oleh suatu fungsi karena API tersebut dapat memunculkan pengecualian yang diperiksa yang secara diam-diam diizinkan untuk disebarkan oleh Kotlin.
Salinan defensif
Saat menampilkan koleksi hanya baca bersama atau yang tidak dimiliki dari API publik, masukkan koleksi ke container yang tidak dapat dimodifikasi atau lakukan penyalinan defensif. Meskipun Kotlin menerapkan properti hanya baca, pada sistem Java tidak ada penerapan seperti itu. Tanpa wrapper atau salinan defensif, invarian dapat dilanggar dengan menampilkan referensi koleksi berumur panjang.
Fungsi pendamping
Fungsi publik dalam objek pendamping harus dianotasikan dengan @JvmStatic
agar diekspos sebagai metode statis.
Tanpa anotasi, fungsi ini hanya tersedia sebagai metode instance pada kolom Companion
statis.
Salah: tidak ada anotasi
class KotlinClass { companion object { fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.Companion.doWork(); } }
Benar: anotasi @JvmStatic
class KotlinClass { companion object { @JvmStatic fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.doWork(); } }
Konstanta pendamping
Properti publik, non-const
yang merupakan konstanta efektif dalam companion object
harus dianotasi dengan @JvmField
agar diekspos sebagai kolom statis.
Tanpa anotasi, properti ini hanya tersedia sebagai instance "getter" yang bernama aneh di kolom Companion
statis. Menggunakan @JvmStatic
alih-alih @JvmField
akan memindahkan "getter" bernama aneh ke metode statis di class, yang masih salah.
Salah: tidak ada anotasi
class KotlinClass { companion object { const val INTEGER_ONE = 1 val BIG_INTEGER_ONE = BigInteger.ONE } }
public final class JavaClass { public static void main(String... args) { System.out.println(KotlinClass.INTEGER_ONE); System.out.println(KotlinClass.Companion.getBIG_INTEGER_ONE()); } }
Salah: anotasi @JvmStatic
class KotlinClass { companion object { const val INTEGER_ONE = 1 @JvmStatic val BIG_INTEGER_ONE = BigInteger.ONE } }
public final class JavaClass { public static void main(String... args) { System.out.println(KotlinClass.INTEGER_ONE); System.out.println(KotlinClass.getBIG_INTEGER_ONE()); } }
Benar: anotasi @JvmField
class KotlinClass { companion object { const val INTEGER_ONE = 1 @JvmField val BIG_INTEGER_ONE = BigInteger.ONE } }
public final class JavaClass { public static void main(String... args) { System.out.println(KotlinClass.INTEGER_ONE); System.out.println(KotlinClass.BIG_INTEGER_ONE); } }
Penamaan idiomatis
Kotlin memiliki konvensi pemanggilan yang berbeda dari Java yang dapat mengubah cara Anda menamai fungsi. Gunakan @JvmName
untuk mendesain nama sehingga akan terasa idiomatis untuk kedua konvensi bahasa atau untuk mencocokkan penamaan library standar masing-masing.
Hal ini paling sering terjadi pada fungsi ekstensi dan properti ekstensi karena lokasi jenis penerimanya berbeda.
sealed class Optionaldata class Some (val value: T): Optional () object None : Optional () @JvmName("ofNullable") fun T?.asOptional() = if (this == null) None else Some(this)
// FROM KOTLIN: fun main(vararg args: String) { val nullableString: String? = "foo" val optionalString = nullableString.asOptional() }
// FROM JAVA: public static void main(String... args) { String nullableString = "Foo"; OptionaloptionalString = Optionals.ofNullable(nullableString); }
Overload fungsi untuk default
Fungsi dengan parameter yang memiliki nilai default harus menggunakan @JvmOverloads
.
Tanpa anotasi ini, fungsi ini tidak dapat dipanggil menggunakan nilai default apa pun.
Saat menggunakan @JvmOverloads
, periksa metode yang dihasilkan untuk memastikan keduanya logis. Jika tidak, lakukan salah satu atau kedua pemfaktoran ulang berikut sampai terpenuhi:
- Ubah urutan parameter agar lebih sesuai dengan default yang ada di bagian akhir.
- Pindahkan default ke overload fungsi manual.
Salah: Tidak ada @JvmOverloads
class Greeting { fun sayHello(prefix: String = "Mr.", name: String) { println("Hello, $prefix $name") } }
public class JavaClass { public static void main(String... args) { Greeting greeting = new Greeting(); greeting.sayHello("Mr.", "Bob"); } }
Benar: anotasi @JvmOverloads
.
class Greeting { @JvmOverloads fun sayHello(prefix: String = "Mr.", name: String) { println("Hello, $prefix $name") } }
public class JavaClass { public static void main(String... args) { Greeting greeting = new Greeting(); greeting.sayHello("Bob"); } }
Pemeriksaan Lint
Persyaratan
- Versi Android Studio: 3.2 Canary 10 atau yang lebih baru
- Versi Plugin Android Gradle: 3.2 atau yang lebih baru
Pemeriksaan yang Didukung
Sekarang ada pemeriksaan Android Lint yang akan membantu Anda mendeteksi dan menandai beberapa masalah interoperabilitas yang dijelaskan di atas. Saat ini hanya masalah di Java (untuk pemakaian Kotlin) yang terdeteksi. Secara khusus, pemeriksaan yang didukung adalah:
- Nullness yang Tidak Diketahui
- Akses Properti
- Tidak ada kata kunci keras Kotlin
- Parameter Lambda Akhir
Android Studio
Untuk mengaktifkan pemeriksaan ini, buka File > Preferences > Editor > Inspections lalu periksa aturan yang ingin Anda aktifkan pada Interoperabilitas Kotlin:
Gambar 1. Setelan interoperabilitas Kotlin di Android Studio.
Setelah memeriksa aturan yang ingin diaktifkan, pemeriksaan baru akan berjalan saat Anda menjalankan pemeriksaan kode (Analyze > Inspect Code…)
Build command line
Untuk mengaktifkan pemeriksaan ini dari build command line, tambahkan baris berikut
di file build.gradle
Anda:
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Untuk sekumpulan konfigurasi lengkap yang didukung di dalam lintOptions, lihat Referensi Gradle DSL Android.
Lalu jalankan ./gradlew lint
dari command line.