Bergabunglah bersama kami di ⁠#Android11: The Beta Launch Show pada tanggal 3 Juni!

Panduan interop Kotlin-Java

Dokumen ini merupakan sekumpulan aturan untuk menulis API publik di 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 yang sulit

Jangan gunakan kata kunci yang sulit 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(/* … */)

Hindari 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.

Anotasi paket JSR 305 dapat digunakan untuk menyiapkan default yang wajar, tetapi saat ini tidak disarankan. Anotasi tersebut memerlukan flag ikut serta agar diterima compiler dan bertentangan dengan sistem modul Java 9.

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 static  Flowable 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.active // Invokes user.isActive()

Metode mutator yang terkait memerlukan awalan ‘set’.

public final class User {
  public String getName() { /* … */ }
  public void setName(String name) { /* … */ }
}
user.name = "Bob" // Invokes user.setName(String)

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) di 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, di mana 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 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 Optional
data 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";
    Optional optionalString =
          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
  • Akhir Parameter Lambda

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:

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.