Przewodnik interoperacyjności Kotlin-Java

Ten dokument jest zbiorem reguł dotyczących tworzenia publicznych interfejsów API w Javie i Kotlinie w taki sposób, aby kod po użyciu z innego kodu miał wrażenie idiomatycznego język.

Ostatnia aktualizacja: 29.07.2024

Java (na potrzeby konsumpcji Kotlin)

Bez sztywnych słów kluczowych

Nie używaj żadnych twardych słów kluczowych Kotlina jako nazwy metod. lub polach. Wymagają one użycia grawisów w celu zmiany znaczenia przy wywołaniu od Kotlin. Opcjonalne słowa kluczowe, słowa kluczowe modyfikujące oraz specjalne identyfikatory są dozwolone.

Na przykład funkcja when Mockito wymaga grawisu, gdy jest używana w Kotlin:

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

Unikaj nazw rozszerzeń Any

Unikaj używania nazw funkcji rozszerzeń w Any w przypadku lub nazwy właściwości rozszerzeń w Any dla o ile nie jest to absolutnie konieczne. Metody i pola członków zawsze będą mają pierwszeństwo przed funkcjami lub właściwościami rozszerzeń Any, może być trudno było odczytać kod, żeby rozpoznać, który z nich jest wywoływany.

Adnotacje dotyczące wartości null

Każdy niepodstawowy parametr, zwrot i typ pola w publicznym interfejsie API powinien zawierają adnotację dotyczącą wartości null. Typy bez adnotacji są interpretowane jako "platforma" o niejednoznacznej dopuszczalności wartości null.

Domyślnie flagi kompilatora Kotlin promują adnotacje JSR 305, ale je oznaczają z ostrzeżeniami. Możesz też ustawić flagę, by kompilator traktował jako błędów.

Parametry lambda na końcu

Typy parametrów odpowiednie do konwersji SAM powinny być ostatnie.

Na przykład podpis metody Flowable.create() w RxJava 2 jest zdefiniowany w następujący sposób:

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

Ponieważ FlowableOnsubscribe kwalifikuje się do konwersji SAM, wywołania funkcji ta metoda z Kotlina wygląda tak:

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

Jeśli jednak parametry zostały odwrócone w podpisie metody, wywołania funkcji można użyć składni lambda na końcu:

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

Prefiksy usługi

Aby metoda została przedstawiona jako właściwość w Kotlin, ścisły styl typu „fasola” należy użyć przedrostka.

Metody metody dostępu wymagają prefiksu get, a w przypadku metod zwracających wartość logiczną – is przedrostek.

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

Powiązane metody mutatorów wymagają prefiksu 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)

Jeśli chcesz udostępniać metody jako właściwości, nie używaj niestandardowych prefiksów, takich jak has, set lub metody dostępu bez prefiksu get. Metody z niestandardowymi prefiksami są wciąż wywoływane jako funkcje, które mogą być akceptowalne w zależności zasady działania tej metody.

Przeciążenie operatora

Zwróć uwagę na nazwy metod, które zezwalają na specjalną składnię wywołania witryny (np. przeciążenie operatorów w Kotlin). Sprawdź, czy metody o nazwach więc warto użyć skróconej składni.

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 (do użytkowania Java)

Nazwa pliku

Gdy plik zawiera funkcje lub właściwości najwyższego poziomu, zawsze dodawaj do nich adnotacje używając @file:JvmName("Foo"), aby wybrać ładną nazwę.

Domyślnie członkowie najwyższego poziomu w pliku Mojaklasa.kt trafią do klasy o nazwie MyClassKt, który jest nieatrakcyjny i wycieka język jako implementacja szczegóły.

Rozważ dodanie „@file:JvmMultifileClass”, aby połączyć członków najwyższego poziomu z grupy wiele plików na jedne zajęcia.

Argumenty lambda

Interfejsy z jedną metodą (SAM) zdefiniowane w Javie można zaimplementować zarówno w Kotlin i Java za pomocą składni lambda, która umieszcza ją w argumencie idiomatycznym. sposób. Kotlin udostępnia kilka opcji definiowania takich interfejsów, z których każda wymaga różnicy.

Preferowana definicja

Funkcje wyższego rzędu przeznaczone do wykorzystania w Javie. nie powinien przyjmować typów funkcji, które zwracają wartość Unit, ponieważ wymaga, aby obiekty wywołujące Java zwracały wartość Unit.INSTANCE. Zamiast wbudowywać funkcję wpisz podpis, użyj funkcjonalnych interfejsów (SAM). Poza tym rozważ użycie funkcjonalnych interfejsów (SAM) zamiast standardowych podczas definiowania interfejsów, które powinny być używane jako lambda, który pozwala na idiomatyczne wykorzystanie języka Kotlin.

Spójrzmy na tę definicję Kotlina:

fun interface GreeterCallback {
  fun greetName(String name)
}

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

Po wywołaniu z Kotlin:

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

W przypadku wywołania z Javy:

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

Nawet jeśli typ funkcji nie zwraca parametru Unit, nadal może być on dobry. pomysłu na utworzenie nazwanego interfejsu, który umożliwi użytkownikom jego wdrożenie przy użyciu , a nie tylko lambda (zarówno w Kotlin, jak i w Javie).

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

Unikaj typów funkcji, które zwracają wartość Unit

Spójrzmy na tę definicję Kotlina:

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

Wymaga zwracania przez elementy wywołujące Javy Unit.INSTANCE:

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

Unikaj interfejsów funkcjonalnych, gdy implementacja ma mieć stan

Kiedy implementacja interfejsu ma mieć określony stan, za pomocą funkcji lambda która nie ma sensu. Porównywalny to ważny przykład: ponieważ ma porównać wartości this z other, a lambda nie zawierają parametru this. Nie wprowadzenie przedrostka fun w interfejsie wymusza użycie elementu wywołującego object : ... składni, która pozwala określić stan i zapewnia wskazówkę dla wywołującego.

Spójrzmy na tę definicję Kotlina:

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

Zapobiega składni lambda w Kotlin, wymagając tej dłuższej wersji:

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

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

Unikaj reklam ogólnych (Nothing)

Typ, którego ogólny parametr to Nothing, jest udostępniany w Javie w postaci nieprzetworzonej. Nierafinowane są rzadko używane w Javie i należy ich unikać.

Wyjątki dla dokumentów

Funkcje, które mogą zgłaszać zaznaczone wyjątki, powinny dokumentować je za pomocą @Throws Wyjątki środowiska wykonawczego powinny być udokumentowane w KDoc.

Zwracaj uwagę na interfejsy API, do których funkcja przekazuje dostęp, ponieważ mogą one zgłaszać zaznaczone wyjątki, na które Kotlin może jednak dyskretnie zezwolić na ich rozpowszechnianie.

Kopie w obronie

Gdy zwracasz udostępnione lub nienależące do Ciebie kolekcje z publicznych interfejsów API, zapakuj je w pojemnikach, których nie można zmienić, ani wykonać kopii obronnej. Pomimo Kotlina nie egzekwuje on właściwości tylko do odczytu, nie jest więc egzekwowane w tym języku z boku strony. Bez otoki lub tekstu defensywnego niezmienniki mogą zostać naruszone przez które zwraca długoterminowe odwołanie do kolekcji.

Funkcje towarzyszące

Funkcje publiczne w obiekcie towarzyszącym muszą mieć adnotację @JvmStatic nie są dostępne jako metoda statyczna.

Bez adnotacji te funkcje są dostępne tylko jako metody instancji w statycznym polu Companion.

Niepoprawnie: brak adnotacji

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

Prawidłowo: adnotacja @JvmStatic

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

Stałe towarzyszące

Publiczne właściwości inne niż const, które są stałymi efektywnymi w companion object, muszą mieć adnotację @JvmField, aby były widoczne jako pole statyczne.

Bez adnotacji te właściwości są dostępne tylko z dziwnymi nazwami instancja „getters” w statycznym polu Companion. Zamiast tego korzystam z @JvmStatic funkcji @JvmField przenosi elementy „getters” o dziwnych nazwach do metod statycznych na zajęciach, co nadal jest nieprawidłowe.

Niepoprawnie: brak adnotacji

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

Nieprawidłowo: adnotacja @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());
    }
}

Prawidłowo: adnotacja @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);
    }
}

Nazewnictwo idiomatyczne

W Kotlin obowiązują inne konwencje wywoływania niż w Javie, co może zmienić funkcji nazw. Użyj @JvmName, aby zaprojektować nazwy tak, aby wyglądały idiomatyczne dla konwencji obu języków lub aby dopasować je do odpowiednich bibliotek standardowych. nazwy.

Najczęściej dzieje się tak w przypadku funkcji i właściwości rozszerzeń bo lokalizacja odbiornika jest inna.

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

Przeciążenia funkcji na potrzeby ustawień domyślnych

Funkcje z parametrami, które mają wartość domyślną, muszą używać parametru @JvmOverloads. Bez tej adnotacji nie można wywołać funkcji za pomocą wartości domyślnych.

Jeśli korzystasz z metody @JvmOverloads, sprawdź wygenerowane metody, aby mieć pewność, że każda mają sens. Jeśli nie, wykonaj co najmniej jedną z tych refaktoryzacji aż do satysfakcji:

  • Zmień kolejność parametrów, tak aby preferować wartości domyślne na ich końcu.
  • Przenieś wartości domyślne do ręcznych przeciążeń funkcji.

Niepoprawnie: nie @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");
    }
}

Poprawnie: adnotacja @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");
    }
}

Kontrola lin

Wymagania

  • Wersja Android Studio: 3.2 Canary 10 lub nowsza
  • Wersja wtyczki Androida do obsługi Gradle: 3.2 lub nowsza

Obsługiwane testy

Dostępne są teraz testy licencji Androida, które pomagają w wykrywaniu i zgłaszaniu niektórych opisane wcześniej problemy ze interoperacyjnością. Tylko problemy w Javie (dla Kotlin) konsumpcji treści). Obsługiwane testy są następujące:

  • Nieznana wartość null
  • Dostęp do usługi
  • Brak słów kluczowych Hard Kotlin
  • Ostatnie parametry lambda

Android Studio,

Aby włączyć tę funkcję, kliknij Plik > Ustawienia > Edytor > inspekcje, Zaznacz reguły, które chcesz włączyć w sekcji Interoperacyjność usługi Kotlin:

Rysunek 1. Ustawienia interoperacyjności z Kotlin w Android Studio.

Gdy zaznaczysz reguły, które chcesz włączyć, nowe weryfikacje będą uruchamianych podczas inspekcji kodu (Analiza > Zbadaj kod...).

Kompilacje z użyciem wiersza poleceń

Aby włączyć te kontrole w kompilacji wiersza poleceń, dodaj następujący wiersz w argumencie Twój plik build.gradle:

Odlotowe

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

Pełny zestaw konfiguracji obsługiwanych w lintOptions znajdziesz w Dokumentacja DSL Gradle dotycząca Androida.

Następnie uruchom ./gradlew lint z poziomu wiersza poleceń.