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

Bu belge, Java ve Kotlin'de herkese açık API'ler oluşturmayla ilgili bir dizi kuraldır. Amaç, kodun diğer dilde kullanıldığında deyimsel olarak algılanmasını sağlamaktır.

Java (Kotlin tüketimi için)

Sert anahtar kelime yok

Kotlin'in sert anahtar kelimelerinden hiçbirini yöntemlerin veya alanların adı olarak kullanmayın. Bunlar, Kotlin'den çağrılırken ters tırnak işareti kullanılarak çıkış karakteri olarak kullanılmalıdır. Yumuşak anahtar kelimelere, değiştirici anahtar kelimelere ve özel tanımlayıcılara izin verilir.

Örneğin, Mockito'nun when işlevi Kotlin'de kullanıldığında ters tırnak gerektirir:

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

Any uzantılı adlardan kaçının

Kesinlikle gerekli olmadığı sürece yöntemler için Any üzerindeki uzantı işlevlerinin adlarını veya alanlar için Any üzerindeki uzantı özelliklerinin adlarını kullanmaktan kaçının. Üye yöntemleri ve alanları her zaman Any'ın uzantı işlevlerinden veya özelliklerinden öncelikli olsa da kodu okurken hangisinin çağrıldığını bilmek zor olabilir.

Null değer alabilme ek açıklamaları

Herkese açık bir genel API'deki her ilkel olmayan parametre, dönüş ve alan türünde null değer alabilme ek açıklaması olmalıdır. Açıklama eklenmemiş türler, boş değer alabilme durumu belirsiz olan "platform" türleri olarak yorumlanır.

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

Lambda parametreleri en son

SAM dönüşümü için uygun parametre türleri en sonda olmalıdır.

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

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

FlowableOnSubscribe, SAM dönüştürme için uygun olduğundan Kotlin'deki 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) { /* … */ }

Tesis önekleri

Bir yöntemin Kotlin'de özellik olarak gösterilmesi için katı "bean" tarzı önekler kullanılmalıdır.

Erişimci yöntemleri için get öneki gerekir. Boole değeri döndüren yöntemler için is öneki 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 mutasyon yöntemleri için 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)

Yöntemlerin özellik olarak gösterilmesini istiyorsanız has, set gibi standart olmayan ön ekleri veya get ön ekli olmayan erişimcileri kullanmayın. Standart olmayan öneklere sahip yöntemler, işlev olarak çağrılabilir. Bu durum, yöntemin davranışına bağlı olarak kabul edilebilir.

Operatör aşırı yüklemesi

Özel çağrı sitesi söz dizimine (ör. Kotlin'de 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ılmasının mantıklı 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, güzel bir ad sağlamak için dosyayı her zaman @file:JvmName("Foo") ile açıklama ekleyin.

Varsayılan olarak, MyClass.kt dosyasındaki üst düzey üyeler, MyClassKt adlı bir sınıfta yer alır. Bu sınıf, dilin bir uygulama ayrıntısı olarak sızmasına neden olur ve çekici değildir.

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

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

Java'da tanımlanan tek yöntemli arayüzler (SAM), Kotlin ve Java'da lambda söz dizimi kullanılarak uygulanabilir. Bu söz dizimi, uygulamayı deyimsel bir şekilde satır içine yerleştirir. Kotlin'de bu tür arayüzleri tanımlamak için birkaç seçenek vardır ve her seçeneğin küçük bir farkı vardır.

Tercih edilen tanım

Java'dan kullanılmak üzere tasarlanan üst düzey işlevler, Unit döndüren işlev türlerini almamalıdır. Aksi takdirde, Java'dan işlev çağıranların Unit.INSTANCE döndürmesi gerekir. İşlev türünü imzada satır içi olarak eklemek yerine işlevsel (SAM) arayüzleri kullanın. Ayrıca, lambda olarak kullanılması beklenen arayüzleri tanımlarken normal arayüzler yerine işlevsel (SAM) arayüzleri kullanmayı da düşünebilirsiniz. Bu, Kotlin'de deyimsel kullanıma olanak tanır.

Şu Kotlin tanımını ele alalım:

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ü Unit döndürmese bile, arayanların bunu yalnızca lambda'larla değil, adlandırılmış bir sınıfla da (hem Kotlin hem de Java'da) uygulayabilmesi için adlandırılmış bir arayüz haline getirmek iyi bir fikir olabilir.

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

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

Şu Kotlin tanımını ele alalım:

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

Java arayanların Unit.INSTANCE döndürmesi gerekir:

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

Uygulamanın durum içermesi gerektiğinde işlevsel arayüzlerden kaçının

Arayüz uygulaması bir duruma sahip olacak şekilde tasarlandığında lambda söz diziminin kullanılması mantıklı değildir. this ile other karşılaştırması yapılması amaçlandığı ve lambda'larda this olmadığı için Comparable, önemli bir örnektir. Arayüzün fun ile başlamaması, arayanın object : ... söz dizimini kullanmasını zorunlu kılar. Bu söz dizimi, arayan kişiye ipucu vererek durum bilgisi sağlamasına olanak tanır.

Şu Kotlin tanımını ele alalım:

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

Kotlin'de lambda söz dizimini engeller ve şu daha uzun sürümün kullanılmasını zorunlu kılar:

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

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

Nothing jeneriklerinden kaçının

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

Belge istisnaları

Kontrollü istisnalar oluşturabilen işlevler, bunları @Throws ile belgelemelidir. Çalışma zamanı istisnaları KDoc'ta belgelenmelidir.

Bir işlevin yetki verdiği API'lerin, Kotlin'in aksi takdirde sessizce yayılmasına izin verdiği kontrol edilmiş istisnalar oluşturabileceğini unutmayın.

Savunma kopyaları

Herkese açık API'lerden paylaşılan veya sahip olunmayan salt okunur koleksiyonlar döndürülürken bunları değiştirilemeyen bir kapsayıcıya sarmalayın ya da savunma amaçlı bir kopya oluşturun. Kotlin, salt okunur özelliklerini zorunlu kılsa da Java tarafında böyle bir zorunluluk yoktur. Sarmalayıcı veya savunma kopyası olmadan, uzun ömürlü bir koleksiyon referansı döndürülerek değişmezler ihlal edilebilir.

Yardımcı işlevler

Arkadaş nesnesindeki herkese açık işlevler, statik yöntem olarak kullanıma sunulmak için @JvmStatic ile açıklama eklenmelidir.

Açıklama olmadan bu işlevler yalnızca statik bir Companion alanında örnek yöntemler olarak kullanılabilir.

Yanlış: Not yok

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

Doğru: @JvmStatic annotation

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çinde etkili sabitler olan herkese açık, const olmayan özellikler, statik alan olarak gösterilmek için @JvmField ile açıklama eklenmelidir.

Açıklama olmadan bu özellikler, yalnızca statik Companion alanında tuhaf adlandırılmış örnek "getters" olarak kullanılabilir. @JvmField yerine @JvmStatic kullanmak, garip adlandırılmış "getters" yöntemlerini sınıftaki statik yöntemlere taşır. Bu da yine yanlıştır.

Yanlış: Not 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 annotation

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ı çağırma kurallarına sahiptir. Bu da işlevleri adlandırma şeklinizi değiştirebilir. Adları, her iki dilin kurallarına uygun olacak veya ilgili standart kitaplık adlandırmasıyla eşleşecek şekilde tasarlamak için @JvmName kullanın.

Bu durum, alıcı türünün konumu farklı olduğundan en sık uzantı işlevleri ve uzantı özellikleri için geçerlidir.

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

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

Varsayılan değere sahip parametreleri olan 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 mantıklı olduğundan emin olun. Aksi takdirde, memnun kalana kadar aşağıdaki yeniden düzenlemelerden birini veya her ikisini birden yapın:

  • Parametre sırasını, varsayılan değerleri sona doğru olanları 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 annotation.

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

Lint kontrolleri

Şartlar

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

Desteklenen kontroller

Artık, daha önce açıklanan birlikte çalışabilirlik sorunlarından bazılarını tespit edip işaretlemenize yardımcı olacak Android Lint kontrolleri var. Yalnızca Java'daki (Kotlin tüketimi için) sorunlar tespit edilir. Desteklenen kontroller şunlardır:

  • Bilinmeyen boşluk
  • Mülk Erişimi
  • No Hard Kotlin anahtar kelimeleri
  • Lambda Parametreleri En Sonda

Android Studio

Bu kontrolleri etkinleştirmek için File > Preferences > Editor > Inspections'a gidin ve Kotlin Interoperability altında etkinleştirmek istediğiniz kuralları işaretleyin:

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

Etkinleştirmek istediğiniz kuralları işaretledikten sonra, kod incelemelerinizi çalıştırdığınızda (Analyze > Inspect Code…) yeni kontroller çalıştırılır.

Komut satırı derlemeleri

Bu kontrolleri 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 içinde desteklenen yapılandırmaların tam listesi için Android Gradle DSL referansına bakın.

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