Panduan interop Kotlin-Java

Dokumen ini adalah sekumpulan aturan untuk menulis API publik di Java dan Kotlin dengan maksud agar kode terasa idiomatis jika dipakai dari gaya lain di bahasa target.

Terakhir diperbarui: 29-07-2024

Java (untuk pemakaian Kotlin)

Tidak ada kata kunci keras

Jangan gunakan kata kunci keras Kotlin sebagai nama metode atau {i>field<i}. Ini membutuhkan penggunaan {i>backtick <i}untuk melarikan diri saat memanggil dari Kotlin. Kata kunci lunak, 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 {i>field<i} kecuali benar-benar diperlukan. Meskipun metode dan kolom anggota akan selalu didahulukan daripada fungsi atau properti ekstensi Any, dapat sulit ketika membaca kode untuk mengetahui kode mana yang dipanggil.

Anotasi nullability

Setiap parameter non-primitif, nilai yang ditampilkan, dan jenis kolom di API publik harus memiliki anotasi nullability. Jenis yang tidak dianotasi diinterpretasikan sebagai "platform" jenis, 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 Flowable.create() RxJava 2 ditentukan sebagai:

public static <T> Flowable<T> create(
    FlowableOnSubscribe<T> source,
    BackpressureStrategy mode) { /* … */ }

Karena FlowableOnSubscribe memenuhi syarat untuk konversi SAM, panggilan fungsi dari metode dari Kotlin akan terlihat seperti ini:

Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)

Namun, jika parameter dibalik dalam tanda tangan metode, panggilan fungsi dapat menggunakan sintaksis lambda akhir:

Flowable.create(BackpressureStrategy.LATEST) { /* … */ }

Awalan properti

Agar metode direpresentasikan sebagai properti di Kotlin, ketat gaya "bean" awalan harus digunakan.

Metode pengakses memerlukan awalan get atau untuk metode yang menampilkan boolean 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 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 non-standar seperti Aksesor berawalan has, set, atau tanpa get. Metode dengan awalan non-standar masih dapat dipanggil sebagai fungsi, yang mungkin dapat diterima bergantung pada perilaku model.

Overload operator

Perhatikan nama metode yang memungkinkan sintaksis situs panggilan khusus (seperti overload operator di Kotlin). Pastikan bahwa nama metode sebagai masuk akal untuk digunakan dengan {i>syntax<i} 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 yang disebut MyClassKt yang tidak menarik dan membocorkan bahasa sebagai penerapan spesifikasi pendukung.

Sebaiknya tambahkan @file:JvmMultifileClass untuk menggabungkan anggota tingkat atas dari beberapa file ke dalam satu kelas.

Argumen lambda

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

Definisi yang lebih disarankan

Fungsi tingkat tinggi yang dimaksudkan untuk digunakan dari Java seharusnya tidak mengambil jenis fungsi yang menampilkan Unit seperti yang memerlukan pemanggil Java untuk menampilkan Unit.INSTANCE. Alih-alih membuat fungsi inline ketik di tanda tangan, gunakan antarmuka fungsional (SAM). Selain itu, pertimbangkan untuk menggunakan antarmuka fungsional (SAM), bukan antarmuka antarmuka 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. Bukan mengawali antarmuka dengan fun akan memaksa pemanggil menggunakan object : ... , yang memungkinkannya memiliki status, 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++
  }
})

Hindari generik Nothing

Jenis yang parameter generiknya adalah Nothing akan diekspos sebagai jenis mentah ke Java. Mentah 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 tidak dimiliki dari API publik, gabungkan mereka dalam kontainer yang tidak dapat dimodifikasi atau melakukan salinan defensif. Terlepas dari Kotlin dengan menerapkan properti hanya-baca, tidak ada penerapan seperti itu pada Java lain. Tanpa pembungkus atau salinan defensif, invarian dapat dilanggar oleh yang menampilkan referensi koleksi berumur panjang.

Fungsi pendamping

Fungsi publik dalam objek pendamping harus dianotasi dengan @JvmStatic 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 dengan nama yang aneh "pengambil" instance di kolom Companion statis. Menggunakan @JvmStatic saja dari @JvmField menggerakkan "pengambil" bernama aneh metode statis pada 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. Menggunakan @JvmName untuk mendesain nama sehingga akan terasa idiomatis untuk kedua konvensi bahasa atau agar sesuai dengan library standar masing-masing penamaan.

Hal ini paling sering terjadi untuk fungsi ekstensi dan properti ekstensi karena lokasi jenis penerima berbeda.

sealed class Optional<T : Any>
data class Some<T : Any>(val value: T): Optional<T>()
object None : Optional<Nothing>()

@JvmName("ofNullable")
fun <T> 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<String> 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 masing-masing masuk akal. Jika tidak, lakukan salah satu atau kedua pemfaktoran ulang berikut hingga terpenuhi:

  • Ubah urutan parameter agar lebih sesuai dengan nilai default berakhir.
  • 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 sebelumnya. Hanya masalah di Java (untuk Kotlin konsumsi) 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 > Preferensi > Editor > Inspeksi dan periksa aturan yang ingin Anda aktifkan pada Interoperabilitas Kotlin:

Gambar 1. Setelan interoperabilitas Kotlin di Android Studio.

Setelah Anda memeriksa aturan yang ingin diaktifkan, pemeriksaan baru akan dijalankan 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.