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

Bu belge, kodun diğer dilde tüketildiğinde deyimsel olarak algılanması amacıyla Java ve Kotlin'de herkese açık API'ler yazmak için bir kurallar kümesidir.

Son güncelleme: 18.05.2018

Java (Kotlin tüketimi için)

Sabit anahtar kelimelere izin verilmez

Yöntem veya alanların adı olarak Kotlin'in kesin anahtar kelimelerinden hiçbirini kullanmayın. Bunlar, Kotlin'den çağrı yaparken kaçmak için vurgu işaretlerinin kullanılmasını gerektirir. Yumuşak anahtar kelimeler, değiştirici anahtar kelimeler ve özel tanımlayıcılara izin verilir.

Örneğin, Mockito'nun when işlevi, Kotlin'den kullanıldığında vurgu işaretlerinin kullanılmasını 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 üzerindeki uzantı işlevlerinin adlarını veya kesinlikle gerekli olmadığı sürece alanlar için Any'deki uzantı özelliklerinin adlarını kullanmaktan kaçının. Üye yöntemleri ve alanları, her zaman Any uzantısının uzantı işlevlerine veya özelliklerine göre önceliklidir, ancak kodu okurken hangisinin çağrıldığını anlamak zor olabilir.

Boş değer atanabilirliği ek açıklamaları

Genel bir API'deki primitif olmayan her parametre, dönüş ve alan türü, boş değer atanabilirlik ek açıklamasına sahip olmalıdır. Ek açıklamalı olmayan türler, belirsiz boşluğa sahip "platform" türleri olarak yorumlanır.

Varsayılan olarak Kotlin derleyici işaretleri, JSR 305 ek açıklamalarını dikkate alır ancak bunları uyarılarla işaretler. Ayrıca derleyicinin ek açıklamaları hata olarak işlemesi için bir işaret de ayarlayabilirsiniz.

Lambda parametreleri en son

SAM dönüşümü için uygun parametre türleri son sırada 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) { /* … */ }

FlowableOnAbone, SAM dönüşümü için uygun olduğundan Kotlin'den bu yöntemin işlev çağrıları şu şekilde görünür:

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

Ancak parametreler yöntem imzasında tersine çevrilmişse, 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 temsil edilmesi için katı "fasulye" tarzı ön ek kullanılmalıdır.

Erişimci yöntemleri "get" ön eki veya boole döndürme yöntemleri için "is" ö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şkili mutatör yöntemleri için bir "set" öneki 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)

Yöntemlerin mülk olarak gösterilmesini istiyorsanız "has"/"set" veya "get" önekli olmayan "get" ön ekleri gibi standart olmayan ön ekleri kullanmayın. Standart olmayan öneklere sahip yöntemler, yöntemin davranışına bağlı olarak kabul edilebilir işlev olarak çağrılabilir.

Operatör aşırı yüklemesi

Kotlin'de ö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 kullanımının 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 kullanımı için)

Dosya adı

Bir dosya üst düzey işlevler veya özellikler içerdiğinde, güzel bir ad vermek için her zaman @file:JvmName("Foo") ile açıklama ekleyin.

Varsayılan olarak, MyClass.kt dosyasındaki üst düzey üyeler MyClassKt adında, cazip olmayan ve uygulama ayrıntısı olarak dili sızdıran bir sınıfa dahil edilir.

Birden fazla dosyanın üst düzey üyelerini tek bir sınıfta birleştirmek için @file:JvmMultifileClass eklemeyi düşünün.

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

Java'da tanımlanan tek yöntem arayüzleri (SAM), uygulamayı deyimsel bir şekilde satır içi yapan lambda söz dizimi kullanılarak hem Kotlin'de hem de Java'da uygulanabilir. Kotlin, bu tür arayüzleri tanımlamak için her biri küçük bir farka sahip birkaç seçenek sunar.

Tercih edilen tanım

Java'dan kullanılması amaçlanan üst düzey işlevler, Java çağrılarının Unit.INSTANCE değerini döndürmesini gerektireceğinden Unit değerini döndüren işlev türlerini almamalıdır. İşlev türünü imzada satır içine eklemek yerine işlevsel (SAM) arayüzleri kullanın. Ayrıca lambda olarak kullanılması beklenen arayüzleri tanımlarken, Kotlin'in deyimsel kullanımına olanak tanırken normal arayüzler yerine işlevsel (SAM) arayüzleri kullanmayı da düşünebilirsiniz.

Şu Kotlin tanımını düşünün:

fun interface GreeterCallback {
  fun greetName(String name)
}

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

Kotlin'den çağrıldığında:

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

Java'dan çağrıldığında:

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

İşlev türü bir Unit döndürmediğinde bile, çağrı yapan kişilerin bunu yalnızca lambda'larla (hem Kotlin hem de Java'da) değil, adlandırılmış bir sınıfla uygulamasına izin vermek için adlandırılmış arayüz yapmak iyi bir fikir olabilir.

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

Unit değerini döndüren işlev türlerinden kaçının

Şu Kotlin tanımını düşünün:

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

Java çağrılarının Unit.INSTANCE değerini döndürmesini gerektirir:

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

Uygulamanın belirli bir görüntüye sahip olması gerektiğinde işlevsel arayüzlerden kaçının.

Arayüz uygulamasının bir durumu olması gerektiğinde, lambda söz diziminin kullanılması anlamlı olmaz. Karşılaştırılabilir, this ile other karşılaştırılması amaçlandığından ve lambda'larda this olmadığı için belirgin bir örnektir. Arayüzün önüne fun getirilmemesi, çağrıyı yapanı object : ... söz dizimini kullanmaya zorlar. Bu da, arayüzün durum bilgisine sahip olmasını sağlayarak arayana ipucu sağlar.

Şu Kotlin tanımını düşünün:

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

Kotlin'de lambda söz dizimini engelleyerek aşağıdaki daha uzun sürümü gerektirir:

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

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

Nothing genel içerikten kaçının

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

Doküman istisnaları

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

Kotlin'in aksi halde sessizce yaymasına izin verdiği işaretli istisnaları verebileceğinden, bir işlevin yetki verdiği API'lere dikkat edin.

Savunma kopyaları

Herkese açık API'lerden paylaşılan veya sahip olmadığınız salt okunur koleksiyonları geri döndürüyorsanız bunları değiştirilemez bir container'a sarmalayın veya savunma amaçlı bir kopya oluşturun. Kotlin, salt okunur özelliklerini zorunlu kılmasına rağmen, Java tarafında böyle bir zorunluluk yoktur. Sarmalayıcı veya savunma metni olmadan, uzun ömürlü bir koleksiyon referansı döndürülerek değişmez değerler ihlal edilebilir.

Tamamlayıcı işlevler

Tamamlayıcı nesnedeki herkese açık işlevlere statik yöntem olarak sunulmaları için @JvmStatic ek açıklaması eklenmelidir.

Ek açıklama olmadan, bu işlevler yalnızca statik 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 notu

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

Tamamlayıcı sabit değerleri

Bir companion object içinde etkili sabitler olan herkese açık, const olmayan mülklere statik alan olarak gösterilmeleri için @JvmField ile not eklenmelidir.

Ek açıklama olmadan, bu özellikler statik Companion alanında yalnızca garip adlandırılmış örnek "getters" olarak kullanılabilir. @JvmField yerine @JvmStatic kullanıldığında, garip olarak adlandırılan "getters" sınıftaki statik yöntemlere taşınır. Bu yöntem 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 notu

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 notu

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'in Java'dan farklı çağırma kuralları vardır. Bu da işlevleri adlandırma şeklinizi değiştirebilir. Her iki dilin kurallarına uygun veya ilgili standart kitaplık adlandırmalarıyla eşleşecek şekilde adlar tasarlamak için @JvmName kullanın.

Bu durum, alıcı türünün konumu farklı olduğundan, en çok uzantı işlevleri ve uzantı özelliklerinde 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üklemeleri

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

@JvmOverloads kullanırken oluşturulan yöntemleri inceleyerek her birinin anlamlı olduğundan emin olun. Uyuşmazsa memnun kalana kadar aşağıdaki yeniden düzenleme işlemlerinden birini veya ikisini birden yapın:

  • Parametre sırasını, varsayılanları sona eren parametreleri tercih edecek şekilde değiştirin.
  • Varsayılanları 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 notu.

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 Döşeme Kontrolleri

Şartlar

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

Desteklenen Kontroller

Artık yukarıda açıklanan birlikte çalışabilirlik sorunlarından bazılarını tespit etmenize ve işaretlemenize yardımcı olacak Android Lint kontrolleri mevcuttur. Şu anda yalnızca Java'daki sorunlar (Kotlin tüketimi için) algılanmaktadır. Desteklenen kontroller şunlardır:

  • Bilinmeyen Boşluk
  • Mülk Erişimi
  • Hard Kotlin anahtar kelimeleri yok
  • Lambda Parametrelerinin Son Değeri

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ı kontrol edin:

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

Etkinleştirmek istediğiniz kuralları kontrol ettikten sonra, kod denetlemelerinizi çalıştırdığınızda yeni denetimler çalışır (Analiz > Kodu İncele...)

Komut satırı derlemeleri

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

Modern

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

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

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