Dieses Dokument enthält Regeln für die Entwicklung öffentlicher APIs in Java und Kotlin mit dem Ziel, dass der Code in der anderen Sprache idiomatisch wirkt.
Letzte Aktualisierung: 18.05.2018
Java (für Kotlin-Nutzung)
Keine festen Keywords
Verwenden Sie keine harten Schlüsselwörter aus Kotlin als Namen von Methoden oder Feldern. Bei Aufrufen aus Kotlin ist dafür die Verwendung von Gravis als Escapezeichen erforderlich. Softe Keywords, Keywords mit Modifizierer und spezielle Kennungen sind zulässig.
Für die when
-Funktion von Mockito sind beispielsweise Backticks erforderlich, wenn sie aus Kotlin verwendet wird:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Keine Any
-Erweiterungsnamen verwenden
Verwenden Sie die Namen der Erweiterungsfunktionen in Any
nicht für Methoden oder die Namen der Erweiterungsattribute in Any
für Felder, sofern dies nicht unbedingt erforderlich ist. Mitgliedsmethoden und -felder haben zwar immer Vorrang vor den Erweiterungsfunktionen oder -eigenschaften von Any
, aber beim Lesen des Codes kann es schwierig sein, zu erkennen, welche aufgerufen wird.
Annotationen zur Null-Zulässigkeit
Für alle nicht-primitiven Parameter, Rückgaben und Feldtypen in einer öffentlichen API sollte eine Annotation zur Null-Zulässigkeit vorhanden sein. Nicht annotierte Typen werden als Plattformtypen interpretiert, die mehrdeutige Null-Zulässigkeit haben.
Standardmäßig berücksichtigen die Kotlin-Compiler-Flags JSR 305-Annotationen, kennzeichnen sie jedoch mit Warnungen. Sie können auch ein Flag festlegen, damit der Compiler die Annotationen als Fehler behandelt.
Lambda-Parameter zuletzt
Parametertypen, die für die SAM-Konvertierung infrage kommen, müssen zuletzt verwendet werden.
Die Methodensignatur RxJava 2’s Flowable.create()
ist beispielsweise so definiert:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Da FlowableOnSubscribe für SAM-Konvertierung infrage kommt, sehen Funktionsaufrufe dieser Methode aus Kotlin wie folgt aus:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Würden die Parameter in der Methodensignatur jedoch umgekehrt, können Funktionsaufrufe die nachgestellte Lambda-Syntax verwenden:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Attributpräfixe
Damit eine Methode in Kotlin als Property dargestellt werden kann, muss ein striktes Präfix im Bean-Stil verwendet werden.
Zugriffsmethoden erfordern ein „get“-Präfix oder für boolesche Rückgabemethoden kann ein „is“-Präfix verwendet werden.
public final class User { public String getName() { /* … */ } public boolean isActive() { /* … */ } }
val name = user.name // Invokes user.getName() val active = user.isActive // Invokes user.isActive()
Verknüpfte Mutatormethoden erfordern ein "set"-Präfix.
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 Sie möchten, dass Methoden als Eigenschaften verfügbar gemacht werden, verwenden Sie keine nicht standardmäßigen Präfixe wie „has“/„set“ oder andere Zugriffsmethoden mit dem Präfix „get“. Methoden mit nicht standardmäßigen Präfixen können dennoch als Funktionen aufgerufen werden, die je nach Verhalten der Methode akzeptabel sein können.
Überlastung durch Operator
Achten Sie auf Methodennamen, die eine spezielle Syntax für Aufrufwebsites in Kotlin zulassen (z.B. Überlastung durch Operatoren). Stellen Sie sicher, dass Methodennamen als solche 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-Nutzung)
Dateiname
Wenn eine Datei Funktionen oder Eigenschaften der obersten Ebene enthält, sollten Sie sie immer mit @file:JvmName("Foo")
annotieren, um einen schönen Namen zu erhalten.
Standardmäßig gelangen Mitglieder der obersten Ebene in einer Datei MyClass.kt in eine Klasse namens MyClassKt
, die nicht ansprechend ist und die Sprache als Implementierungsdetail leakt.
Sie können @file:JvmMultifileClass
hinzufügen, um die Mitglieder der obersten Ebene aus mehreren Dateien in einer einzigen Klasse zusammenzufassen.
Lambda-Argumente
In Java definierte Single-Method Interfaces (SAM) können mithilfe der Lambda-Syntax sowohl in Kotlin als auch in Java implementiert werden. Dabei wird die Implementierung idiomatisch inline implementiert. Kotlin bietet mehrere Optionen zum Definieren solcher Schnittstellen, die sich jeweils geringfügig unterscheiden.
Bevorzugte Definition
Funktionen höherer Ordnung, die in Java verwendet werden sollen, sollten keine Funktionstypen verwenden, die Unit
zurückgeben, da Java-Aufrufer dafür erforderlich sind, Unit.INSTANCE
zurückzugeben.
Verwenden Sie funktionale SAM-Schnittstellen, anstatt den Funktionstyp in die Signatur einzufügen.
Erwägen Sie auch die Verwendung von funktionalen Schnittstellen (SAM) anstelle von regulären Schnittstellen, wenn Sie Schnittstellen definieren, die voraussichtlich als Lambdas verwendet werden. Dies ermöglicht eine idiomatische Nutzung aus Kotlin.
Betrachten Sie diese Kotlin-Definition:
fun interface GreeterCallback { fun greetName(String name) } fun sayHi(greeter: GreeterCallback) = /* … */
Bei Aufruf über Kotlin:
sayHi { println("Hello, $it!") }
Bei Aufruf aus Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Auch wenn der Funktionstyp kein Unit
zurückgibt, ist es möglicherweise dennoch eine gute Idee, ihn als benannte Schnittstelle zu verwenden, damit Aufrufer sie mit einer benannten Klasse und nicht nur mit Lambdas (sowohl in Kotlin als auch in Java) implementieren können.
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 Unit.INSTANCE
zurückgeben:
sayHi(name -> { System.out.println("Hello, " + name + "!"); return Unit.INSTANCE; });
Funktionale Schnittstellen vermeiden, wenn die Implementierung einen Status haben soll
Wenn die Schnittstellenimplementierung einen Zustand haben soll, ist die Verwendung der Lambda-Syntax nicht sinnvoll.
Vergleichbar ist ein auffälliges Beispiel, da es this
mit other
vergleichen soll und Lambdas nicht this
haben. Wenn der Schnittstelle fun
nicht vorangestellt wird, wird der Aufrufer gezwungen, die Syntax object : ...
zu verwenden, die einen Status zulässt und dem Aufrufer einen Hinweis gibt.
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
-Generika vermeiden
Ein Typ mit dem generischen Parameter Nothing
wird als Rohtypen für Java verfügbar gemacht. Raw-Typen werden in Java selten verwendet und sollten vermieden werden.
Dokumentausnahmen
Funktionen, die überprüfte Ausnahmen auslösen können, sollten mit @Throws
dokumentiert werden. Laufzeitausnahmen sollten in KDoc dokumentiert werden.
Achten Sie auf die APIs, an die eine Funktion delegiert, da sie geprüfte Ausnahmen auslösen können, die von Kotlin ansonsten unbemerkt weitergegeben werden können.
Abwehrkopien
Wenn Sie freigegebene oder unbeanspruchte schreibgeschützte Sammlungen von öffentlichen APIs zurückgeben, verpacken Sie sie in einen nicht änderbaren Container oder führen Sie eine defensive Kopie aus. Obwohl Kotlin die schreibgeschützte Property erzwingt, gibt es auf Java-Seite keine solche Erzwingung. Ohne den Wrapper oder die Defensivkopie können Invarianten durch Rückgabe einer langlebigen Sammlungsreferenz verletzt werden.
Companion-Funktionen
Öffentliche Funktionen in einem Companion-Objekt müssen mit @JvmStatic
annotiert werden, damit sie als statische Methode verfügbar gemacht werden.
Ohne die Annotation sind diese Funktionen nur als Instanzmethoden in einem statischen Companion
-Feld verfügbar.
Falsch: keine Anmerkung
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
sind, die effektive Konstanten in einer companion object
sind, müssen mit @JvmField
annotiert werden, um als statisches Feld verfügbar gemacht zu werden.
Ohne die Annotation sind diese Attribute nur als Instanz-Getter mit seltsamen Namen im statischen Feld Companion
verfügbar. Wenn Sie @JvmStatic
anstelle von @JvmField
verwenden, werden die seltsam benannten "Getter" in statische Methoden der Klasse verschoben. Dies ist immer noch falsch.
Falsch: keine Anmerkung
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 die Art der Benennung von Funktionen ändern kann. Verwenden Sie @JvmName
, um Namen so zu gestalten, dass sie sowohl für die Sprachkonventionen als auch für die Benennung der jeweiligen Standardbibliothek idiomatisch erscheinen.
Das ist am häufigsten bei Erweiterungsfunktionen und Erweiterungseigenschaften der Fall, weil sich der Empfängertyp jeweils von einem anderen Standort unterscheidet.
sealed class Optionaldata 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"; OptionaloptionalString = Optionals.ofNullable(nullableString); }
Funktionsüberladungen für Standardeinstellungen
Funktionen mit Parametern, die einen Standardwert haben, müssen @JvmOverloads
verwenden.
Ohne diese Annotation ist es unmöglich, die Funktion mit Standardwerten aufzurufen.
Prüfen Sie bei Verwendung von @JvmOverloads
, ob die generierten Methoden jeweils sinnvoll sind. Ist dies nicht der Fall, führen Sie eine oder beide der folgenden Refaktorierungen durch, bis Sie zufrieden sind:
- Ändern Sie die Reihenfolge der Parameter so, dass die Standardparameter am Ende bevorzugt werden.
- 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üfungen
Voraussetzungen
- Android Studio-Version:3.2 Canary 10 oder höher
- Version des Android-Gradle-Plug-ins:3.2 oder höher
Unterstützte Prüfungen
Es gibt jetzt Android Lint-Prüfungen, mit denen Sie einige der oben beschriebenen Interoperabilitätsprobleme erkennen und melden können. Derzeit werden nur Probleme in Java (für die Kotlin-Nutzung) erkannt. Folgende Prüfungen werden unterstützt:
- Unbekannte NULL-Werte
- Property-Zugriff
- Keine festen Kotlin-Schlüsselwörter
- Lambda-Parameter zuletzt
Android Studio
Rufen Sie Datei > Einstellungen > Editor > Inspektionen auf und prüfen Sie unter „Kotlin-Interoperabilität“ die Regeln, die Sie aktivieren möchten:
Nachdem Sie die Regeln geprüft haben, die Sie aktivieren möchten, werden die neuen Prüfungen bei der Codeprüfung (Analysieren > Code prüfen...) ausgeführt.
Befehlszeilen-Builds
Fügen Sie der Datei build.gradle
die folgende Zeile hinzu, um diese Prüfungen über die Befehlszeilen-Builds zu aktivieren:
Groovig
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Die vollständigen Konfigurationen, die in lintOptions unterstützt werden, finden Sie in der Gradle-DSL-Referenz für Android.
Führen Sie dann ./gradlew lint
über die Befehlszeile aus.