Panduan interop Kotlin-Java

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

Antarmuka metode tunggal (SAM) yang ditentukan di Java dapat diimplementasikan di Kotlin dan Java menggunakan sintaksis lambda, yang menyisipkan implementasi tersebut secara idiomatis. Kotlin memiliki beberapa opsi untuk menentukan antarmuka tersebut, masing-masing dengan sedikit perbedaan.

Definisi yang lebih disarankan

Fungsi tingkat tinggi yang dimaksudkan untuk digunakan dari Java tidak boleh mengambil jenis fungsi yang menampilkan Unit karena memerlukan pemanggil Java untuk menampilkan Unit.INSTANCE. Gunakan antarmuka fungsional (SAM), bukan menyisipkan jenis fungsi di tanda tangan. Sebaiknya gunakan antarmuka fungsional (SAM), bukan antarmuka reguler, saat menentukan antarmuka yang diharapkan digunakan sebagai lambda, yang memungkinkan penggunaan idiomatis dari Kotlin.

Pertimbangkan definisi Kotlin ini:

fun interface GreeterCallback {
  fun greetName(String name)
}

fun sayHi(greeter: GreeterCallback) = /* … */

Jika dipanggil dari Kotlin:

sayHi { println("Hello, $it!") }

Jika dipanggil dari Java:

sayHi(name -> System.out.println("Hello, " + name + "!"));

Meskipun jenis fungsi tidak menampilkan Unit, sebaiknya jadikan jenis fungsi tersebut sebagai antarmuka bernama agar pemanggil dapat menerapkannya dengan class bernama, bukan hanya lambda (di Kotlin dan Java).

class MyGreeterCallback : GreeterCallback {
  override fun greetName(name: String) {
    println("Hello, $name!");
  }
}

Menghindari jenis fungsi yang menampilkan Unit

Pertimbangkan definisi Kotlin ini:

fun sayHi(greeter: (String) -> Unit) = /* … */

Mengharuskan pemanggil Java untuk menampilkan Unit.INSTANCE:

sayHi(name -> {
  System.out.println("Hello, " + name + "!");
  return Unit.INSTANCE;
});

Menghindari antarmuka fungsional jika implementasinya dimaksudkan untuk memiliki status

Jika implementasi antarmuka dimaksudkan untuk memiliki status, penggunaan sintaksis lambda seharusnya tidak tersedia. Comparable adalah contoh yang jelas karena dimaksudkan untuk membandingkan this dengan other, dan lambda tidak memiliki this. Tidak mengawali antarmuka dengan fun akan memaksa pemanggil untuk menggunakan sintaksis object : ..., yang memungkinkannya memiliki status, yang memberikan petunjuk kepada pemanggil.

Pertimbangkan definisi Kotlin ini:

// No "fun" prefix.
interface Counter {
  fun increment()
}

Mencegah sintaksis lambda di Kotlin, yang memerlukan versi lebih panjang:

runCounter(object : Counter {
  private var increments = 0 // State

  override fun increment() {
    increments++
  }
})

Menghindari 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
  • 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.