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 ini 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 akan 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:
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.