Leitfaden zur Kotlin-Java-Interoperabilität

Dieses Dokument enthält eine Reihe von Regeln für das Erstellen öffentlicher APIs in Java und Kotlin mit der Absicht, dass sich der Code idiomatisch anfühlt, wenn er von der anderen Sprache.

Letzte Aktualisierung: 29.07.2024

Java (für Kotlin)

Keine festen Keywords

Verwenden Sie keine der harten Keywords von Kotlin als Methodennamen. oder Felder. Diese erfordern die Verwendung von Gravis als Escapezeichen, wenn von Kotlin Weiche Keywords, Modifizierer-Keywords und spezielle IDs sind zulässig.

Die when-Funktion von Mockito erfordert beispielsweise Graviszeichen, wenn sie von Kotlin verwendet wird:

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

Any-Erweiterungsnamen vermeiden

Vermeiden Sie die Verwendung der Namen der Erweiterungsfunktionen auf Any für Methoden oder die Namen der Erweiterungs-Properties für Any für enthalten, sofern dies nicht unbedingt erforderlich ist. Member-Methoden und -Felder werden Vorrang vor den Erweiterungsfunktionen oder -eigenschaften von Any haben, beim Lesen des Codes ist schwierig, zu wissen, welcher aufgerufen wird.

Anmerkungen zur Null-Zulässigkeit

Jeder nicht-primitive Parameter, jede Rückgabe und jeder Feldtyp in einer öffentlichen API sollten eine Anmerkung zur Null-Zulässigkeit haben. Nicht annotierte Typen werden als „Plattform“ Typen mit mehrdeutiger Null-Zulässigkeit.

Standardmäßig berücksichtigen die Kotlin-Compiler-Flags JSR 305-Annotationen, kennzeichnen sie aber mit Warnungen. Sie können auch ein Flag setzen, damit der Compiler Annotationen als Fehler.

Lambda-Parameter zuletzt

Parametertypen, die für SAM-Conversions infrage kommen, sollten zuletzt angegeben werden.

Die Methodensignatur Flowable.create() von RxJava 2 ist beispielsweise so definiert:

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

Da FlowableOnSubscribe für die SAM-Konvertierung infrage kommt, werden Funktionsaufrufe von sieht diese Methode von Kotlin so aus:

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

Wurden die Parameter in der Methodensignatur jedoch umgekehrt, werden Funktionsaufrufe könnte die nachgestellte Lambda-Syntax verwenden:

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

Attributpräfixe

Damit eine Methode in Kotlin als Eigenschaft dargestellt wird, muss der strikte Bean-Stil verwendet werden. muss ein Präfix verwendet werden.

Für Zugriffsmethoden ist das Präfix get oder für boolesche Rückgabemethoden ein is erforderlich Präfix verwenden.

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

Für verknüpfte Mutator-Methoden ist das Präfix set erforderlich.

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)

Wenn Methoden als Attribute verfügbar gemacht werden sollen, verwenden Sie keine nicht standardmäßigen Präfixe wie Zugriffsfunktionen mit dem Präfix has, set oder ohne get. Methoden mit nicht standardmäßigen Präfixen können weiterhin als Funktionen aufgerufen werden, was je nach Verhalten der Methode.

Überlastung durch Operator

Achten Sie bei Methodennamen darauf, dass eine spezielle Syntax für Aufruf-Websites möglich ist (z. B. Operator Überlastung in Kotlin) Stellen Sie sicher, dass Methodennamen als mit der gekürzten Syntax sinnvoll sind.

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 (für Java)

Dateiname

Wenn eine Datei Funktionen oder Eigenschaften der obersten Ebene enthält, wird sie immer annotiert. mit @file:JvmName("Foo"), um einen schönen Namen zu erhalten.

Standardmäßig landen die Mitglieder der obersten Ebene in der Datei „MyClass.kt“ in einem Kurs namens MyClassKt, was nicht ansprechend ist und die Sprache als Implementierung sprengt Details.

Du kannst „@file:JvmMultifileClass“ hinzufügen, um die Mitglieder der obersten Ebene aus mehrere Dateien in einer Klasse zusammenfassen.

Lambda-Argumente

In Java definierte Einzelmethodenschnittstellen (SAM) können sowohl in Kotlin implementiert werden als auch und Java mit Lambda-Syntax, was die Implementierung in einer idiomatischen Kotlin bietet mehrere Möglichkeiten, solche Schnittstellen zu definieren, jede mit einem Unterschied.

Bevorzugte Definition

Funktionen höherer Ordnung, die aus Java verwendet werden sollen keine Funktionstypen annehmen, die Unit zurückgeben, erfordern Java-Caller, dass Unit.INSTANCE zurückgegeben wird. Anstatt die Funktion inline einzufügen, Geben Sie die Signatur ein und verwenden Sie funktionale Schnittstellen (SAM). Ebenfalls Verwenden Sie funktionale Schnittstellen (SAM) anstelle von normalen zum Definieren von Schnittstellen, die als Lambdas verwendet werden sollen, die idiomatische Nutzung von Kotlin ermöglicht.

Betrachten Sie diese Kotlin-Definition:

fun interface GreeterCallback {
  fun greetName(String name)
}

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

Bei Aufruf von Kotlin:

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

Bei Aufruf aus Java:

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

Auch wenn der Funktionstyp kein Unit zurückgibt, kann die Funktion trotzdem gut sein. um sie zu einer benannten Schnittstelle zu machen, sodass Aufrufer sie mit einer benannten und nicht nur Lambdas (in Kotlin und Java).

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

Funktionstypen vermeiden, die Unit zurückgeben

Betrachten Sie diese Kotlin-Definition:

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

Java-Aufrufer müssen dafür Unit.INSTANCE zurückgeben:

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

Funktionsfähige Schnittstellen vermeiden, wenn die Implementierung einen Status haben soll

Wenn die Schnittstellenimplementierung einen Status haben soll, indem die Lambda-Funktion verwendet wird ergibt keinen Sinn. Comparable ist ein gutes Beispiel, da this mit other verglichen werden soll und Lambdas nicht this haben. Nicht Wenn fun der Schnittstelle das Präfix fun voranstellt, wird der Aufrufer gezwungen, object : ... zu verwenden , mit der sie einen Zustand haben und einen Hinweis für den Aufrufer liefern kann.

Betrachten Sie diese Kotlin-Definition:

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

Sie verhindert Lambda-Syntax in Kotlin und erfordert diese längere Version:

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

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

Nothing generische Begriffe vermeiden

Ein Typ mit dem generischen Parameter Nothing wird in Java als Rohtypen bereitgestellt. RAW werden in Java selten verwendet und sollten vermieden werden.

Ausnahmen für das Dokument

Funktionen, die geprüfte Ausnahmen auslösen können, sollten diese mit @Throws Laufzeitausnahmen sollten in KDoc dokumentiert werden.

Achten Sie auf die APIs, an die eine Funktion delegiert wird, da diese möglicherweise aktivierte Ausnahmen, die Kotlin ansonsten automatisch übertragen kann.

Verteidigungskopien

Wenn Sie freigegebene oder unbeanspruchte schreibgeschützte Sammlungen von öffentlichen APIs zurückgeben, verpacken Sie in einem nicht änderbaren Container speichern oder eine Verteidigungskopie erstellen. Trotz Kotlin die schreibgeschützte Eigenschaft erzwungen wird, wird dies auf der Java- zu verstehen. Ohne den Wrapper oder die Verteidigungskopie können Invarianten verletzt werden, indem und eine langlebige Sammlungsreferenz zurückgeben.

Companion-Funktionen

Öffentliche Funktionen in einem Companion-Objekt müssen mit @JvmStatic gekennzeichnet werden. als statische Methode bereitgestellt werden.

Ohne die Anmerkung sind diese Funktionen nur als Instanzmethoden verfügbar. für ein statisches Companion-Feld.

Falsch: kein Hinweis

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

Richtig:@JvmStatic Anmerkung

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

Companion-Konstanten

Öffentliche Attribute, die keine const-Attribute sind und gültige Konstanten in einer companion object sind, müssen mit @JvmField annotiert werden, damit sie als statisches Feld bereitgestellt werden können.

Ohne die Anmerkung sind diese Eigenschaften nur als seltsame Namen verfügbar. Instanz-„Getter“ für das statische Feld Companion. Stattdessen wird @JvmStatic verwendet von @JvmField verschiebt die seltsam benannten "getters" statische Methoden für die Klasse, was immer noch falsch ist.

Falsch: kein Hinweis

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

Falsch:@JvmStatic Anmerkung

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

Richtig:@JvmField Anmerkung

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

Idiomatische Benennung

Kotlin hat andere Aufrufkonventionen als Java, was dazu führen kann, wie Sie Namensfunktionen. Verwende @JvmName, um Namen so zu gestalten, dass sie idiomatisch wirken für beide Sprachkonventionen oder für die entsprechende Standardbibliothek Namensgebung.

Dies tritt am häufigsten bei Erweiterungsfunktionen und Erweiterungseigenschaften auf. da der Empfängertyp an einem anderen Ort ist.

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

Funktionsüberlastungen für Standardeinstellungen

Funktionen mit Parametern, die einen Standardwert haben, müssen @JvmOverloads verwenden. Ohne diese Annotation ist es unmöglich, die Funktion mit einer Standardwerten.

Wenn Sie @JvmOverloads verwenden, prüfen Sie die generierten Methoden, um sicherzustellen, dass sie alle sinnvoll sind. Ist dies nicht der Fall, führen Sie eine oder beide der folgenden Refaktorierungen durch Bis zufrieden:

  • Ändern Sie die Reihenfolge der Parameter so, dass diejenigen bevorzugt werden, die standardmäßig in Richtung der enden.
  • Verschieben Sie die Standardwerte in manuelle Funktionsüberlastungen.

Falsch: Nein @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");
    }
}

Richtig:@JvmOverloads Anmerkung.

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

Fusselprüfung

Voraussetzungen

  • Android Studio-Version:3.2 Canary 10 oder höher
  • Android-Gradle-Plug-in-Version:3.2 oder höher

Unterstützte Prüfungen

Es gibt jetzt Android Lint-Prüfungen, mit denen Sie einige die zuvor beschriebenen Interoperabilitätsprobleme. Nur Probleme in Java (für Kotlin) Verbrauch) erkannt werden. Folgende Prüfungen werden unterstützt:

  • Unbekannter Nullwert
  • Property-Zugriff
  • Keine festen Kotlin-Keywords
  • Letzte Lambda-Parameter

Android Studio

Um diese Überprüfungen zu aktivieren, wechseln Sie zu Datei > Einstellungen > Editor > Inspektionen und Markieren Sie die Regeln, die Sie unter Kotlin-Interoperabilität aktivieren möchten:

Abbildung 1: Interoperabilitätseinstellungen für Kotlin in Android Studio

Nachdem Sie die Regeln ausgewählt haben, die Sie aktivieren möchten, werden die neuen Prüfungen werden beim Ausführen Ihrer Codeprüfungen ausgeführt (Analysieren > Code prüfen...).

Befehlszeilen-Builds

Um diese Prüfungen über die Befehlszeilen-Builds zu aktivieren, fügen Sie folgende Zeile Ihre build.gradle-Datei:

Cool

android {

    ...

    lintOptions {
        enable 'Interoperability'
    }
}

Kotlin

android {
    ...

    lintOptions {
        enable("Interoperability")
    }
}

Den vollständigen Satz an Konfigurationen, die in lintOptions unterstützt werden, finden Sie im Gradle DSL-Referenz für Android

Führen Sie dann ./gradlew lint über die Befehlszeile aus.