Kotlin-Java birlikte çalışabilirlik kılavuzu

Bu dokümanda, Java ve Kotlin'de herkese açık API'ler yazmaya yönelik bir dizi kural yer almaktadır. Amaç, kodun diğer dilden kullanıldığında deyimsel olarak hissedilmesidir.

Son güncelleme: 18.05.2018

Java (Kotlin tüketimi için)

Sabit anahtar kelime yok

Yöntem veya alan adı olarak Kotlin katı anahtar kelimelerini kullanmayın. Bu nedenle, Kotlin'den arama yaparken çıkış karakterlerini kullanmanız gerekir. Soft anahtar kelimelere, değiştirici anahtar kelimelere ve özel tanımlayıcılara izin verilir.

Örneğin, Mockito'nun when işlevi, Kotlin'den kullanıldığında vurgu işaretleri gerektirir:

val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)

Any uzantı adından kaçının

Yöntemler için Any üzerinde uzantı işlevlerinin veya Any ürününde uzantı özelliklerinin adlarını kullanmayın. Aksi takdirde kesinlikle gereklidir. Üye yöntemlerinin ve alanlarının, Any uzantı işlevleri veya özelliklerinden her zaman öncelikli olmasına karşın, kodu okurken hangisinin çağrıldığını bilmek zor olabilir.

Boş değerlilik ek açıklamaları

Herkese açık API'deki temel olmayan her parametre, dönüş ve alan türü boş değer ek açıklaması içermelidir. Ek açıklamalı olmayan türler, belirsiz benzersizliğe sahip olan "platform" türleri olarak yorumlanır.

Kotlin derleyici işaretleri varsayılan olarak JSR 305 ek açıklamalarını dikkate alır ancak bunları uyarıyla işaretler. Derleyicinin ek açıklamaları hata olarak değerlendirmesini sağlamak için bir işaret de ayarlayabilirsiniz.

Lambda parametreleri son

SAM dönüşümüne uygun olan parametre türleri son olmalıdır.

Örneğin, RxJava 2’s Flowable.create() yöntem imzası şu şekilde tanımlanır:

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

flowableOnSubscribe, SAM dönüşümü için uygun olduğundan Kotlin'den bu yöntemle yapılan çağrılar aşağıdaki gibi görünür:

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

Ancak yöntem imzasındaki parametreler tersine çevrildiyse işlev çağrıları sondaki lambda söz dizimini kullanabilir:

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

Mülk ön ekleri

Bir yöntemin Kotlin'de mülk olarak gösterilmesi için "fasulye" stilinde ön ek kullanılmalıdır.

Erişimci yöntemleri "get" öneki gerektirir veya boole döndürme yöntemleri için "eşittir" ön eki kullanılabilir.

public final class User {
  public String getName() { /* … */ }
  public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()

İlişkilendirilmiş mutatör yöntemlerinde "set" ön eki gerekir.

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)

Özelliklerin özellikler olarak gösterilmesini istiyorsanız "has"/"set" veya "get" öneki olmayan erişimciler gibi standart dışı önekler kullanmayın. Standart olmayan ön eklere sahip yöntemler, yöntemin davranışına göre kabul edilebilir işlevler olarak çağrılabilir.

Operatör aşırı yüklemesi

Kotlin içinde özel çağrı sitesi söz dizimine (ör. operatör aşırı yüklemesi) izin veren yöntem adlarına dikkat edin. Bu tür yöntem adlarının kısaltılmış söz dizimiyle birlikte anlamlı olduğundan emin olun.

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 (Java tüketimi için)

Dosya adı

Bir dosya üst düzey işlevler veya özellikler içerdiğinde her zaman iyi bir ad sağlamak için dosyaya @file:JvmName("Foo") ile not ekleyin.

Varsayılan olarak, MyClass.kt'deki bir üst düzey üyeler MyClassKt adında bir sınıfa girerler. Bu sınıfta ilgi çekici değildir ve dil kullanımını uygulama ayrıntıları olarak sızdırırlar.

Birden fazla dosyadaki üst düzey üyeleri tek bir sınıfta birleştirmek için @file:JvmMultifileClass öğesini ekleyebilirsiniz.

Lambda bağımsız değişkenleri

Java'dan kullanılması amaçlanan işlev türleri, Unit döndürme türünü kullanmamalıdır. Bu, deyimsel olmayan açık bir return Unit.INSTANCE; ifadesi belirtmeyi gerektirir.

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;
});

Bu söz dizimi, başka türlere uygulanabilecek, semantik olarak adlandırılmış bir türün sağlanmasına da izin vermez.

Latda türü için Kotlin'de adlandırılmış, tek bir soyut yöntem (SAM) arayüzü tanımlamak, Java sorununu düzeltir ancak lambda söz diziminin Kotlin'de kullanılmasını engeller.

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 + "!"))

Java'da adlandırılmış bir SAM arayüzünün tanımlanması, arayüz türünün açıkça belirtilmesi gereken Kotlin lambda söz diziminin biraz düşük kaliteli bir sürümünün kullanılmasına olanak tanır.

// 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 + "!"));

Java Mevcut öneri, dönüş türü Unit olduğunda Java'da bozulmuş deneyime rağmen işlev türünü tercih etmektir.

Nothing genel

Genel parametresi Nothing olan bir tür, Java'ya ham türleri olarak gösterilir. Ham türler Java'da nadiren kullanılır ve kaçınılmalıdır.

Doküman istisnaları

İşaretli istisnalar atabilecek işlevler bunları @Throws ile belgelemelidir. Çalışma zamanı istisnaları KDoc'ta belgelenmelidir.

Kotlin'in aksi halde sessizce yayılmasına izin verdiği kontrol edilen istisnaları tetikleyebileceği için bir işlevin yetki verdiği API'lere dikkat edin.

Savunma kopyaları

Herkese açık API'lerden paylaşılan veya sahibi olmayan salt okunur koleksiyonları döndürürken bunları değiştirilemez bir kapsayıcıya sarmalayın veya bir savunma metni kullanın. Kotlin salt okunur özelliğini uygulasa da Java tarafında böyle bir yaptırım yoktur. Sarmalayıcı veya savunma metni olmadan, uzun ömürlü bir koleksiyon başvurusu döndürülerek değişmezler ihlal edilebilir.

Tamamlayıcı işlevler

Tamamlayıcı nesnedeki herkese açık işlevlere statik yöntem olarak eklenmesi için @JvmStatic ile not eklenmelidir.

Ek açıklama olmadan, bu işlevler yalnızca statik bir Companion alanında örnek yöntemleri olarak kullanılabilir.

Yanlış: ek açıklama yok

class KotlinClass {
    companion object {
        fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.Companion.doWork();
    }
}

Doğru: @JvmStatic ek açıklaması

class KotlinClass {
    companion object {
        @JvmStatic fun doWork() {
            /* … */
        }
    }
}
public final class JavaClass {
    public static void main(String... args) {
        KotlinClass.doWork();
    }
}

Tamamlayıcı sabitler

Bir companion object işlevinde etkin sabit değerler olan const dışındaki herkese açık mülkler, statik alan olarak gösterilecek @JvmField ile ek açıklama eklenmelidir.

Ek açıklama olmadan, bu özellikler yalnızca statik Companion alanında garip şekilde adlandırılmış "alıcılar" olarak kullanılabilir. @JvmField yerine @JvmStatic kullanıldığında garip şekilde adlandırılmış "getget" (sınıfı) statik yöntemlere taşınır. Bu da hâlâ yanlıştır.

Yanlış: ek açıklama yok

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());
    }
}

Yanlış: @JvmStatic ek açıklama

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());
    }
}

Doğru: @JvmField ek açıklaması

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);
    }
}

Deyimsel adlandırma

Kotlin, Java'dan farklı işlevlere sahiptir. Bu kurallar, işlevleri adlandırma şeklinizi değiştirebilir. Her iki dilin kongresinde veya birbiriyle ilişkili standart kitaplık adlarında kullanabilmeleri için adları tasarlamak üzere @JvmName kullanın.

Bu durum, alıcı türünün konumu farklı olduğu için en sık uzantı işlevleri ve uzantı mülklerinde görülür.

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);
}

Varsayılanlar için işlev aşırı yüklemesi

Varsayılan değeri olan parametrelere sahip işlevler @JvmOverloads kullanmalıdır. Bu ek açıklama olmadan, herhangi bir varsayılan değeri kullanarak işlevi çağırmak mümkün değildir.

@JvmOverloads kullanırken, her birinin anlamlı olduğundan emin olmak için oluşturulan yöntemleri inceleyin. Aksi takdirde, memnun oluncaya kadar aşağıdaki yeniden düzenlemelerden birini veya ikisini birden gerçekleştirin:

  • Parametre sırasını, varsayılanları sona doğru olanları tercih edecek şekilde değiştirin.
  • Varsayılan ayarları manuel işlev aşırı yüklemelerine taşıyın.

Yanlış: Hayır @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");
    }
}

Doğru: @JvmOverloads ek açıklaması.

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");
    }
}

Tüy Alma Kontrolleri

Şartlar

  • Android Studio sürümü: 3.2 Canary 10 veya sonraki sürümler
  • Android Gradle Plugin sürümü: 3.2 veya üzeri

Desteklenen Kontroller

Şimdi de yukarıda açıklanan birlikte çalışabilirlik sorunlarından bazılarını tespit edip belirlemenize yardımcı olacak Android Lint kontrolleri var. Şu anda yalnızca Kotlin tüketimi için Java ile ilgili sorunlar tespit edilmektedir. Tam olarak desteklenen kontroller şunlardır:

  • Bilinmeyen Boş Değer
  • Mülk Erişimi
  • Hard Kotlin anahtar kelimesi yok
  • Son Lambda Parametreleri

Android Studio

Bu kontrolleri etkinleştirmek için Dosya > Tercihler > Düzenleyici > Denetimler'e gidin ve Kotlin birlikte çalışabilirliği altında etkinleştirmek istediğiniz kuralları işaretleyin:

Şekil 1. Android Studio'daki Kotlin birlikte çalışabilirlik ayarları.

Etkinleştirmek istediğiniz kuralları kontrol ettiğinizde, kod denetimlerinizi çalıştırdığınızda yeni kontroller çalıştırılır (Analiz > Kodu İncele...)

Komut satırı derlemeleri

Komut satırı derlemelerinden bu denetimleri etkinleştirmek için build.gradle dosyanıza aşağıdaki satırı ekleyin:

Eğlenceli

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

lintOptions'ta desteklenen yapılandırma gruplarının tamamı için Android Gradle DSL referansına bakın.

Ardından, komut satırından ./gradlew lint'u çalıştırın.