Dieses Dokument enthält eine Reihe von Regeln für die Entwicklung ö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 bei einem Aufruf 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) Achten Sie darauf, 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
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-Abruf 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
ergeben. Ist dies nicht der Fall, führen Sie eine oder beide der folgenden Refaktorierungen durch
Bis zufrieden:
- Ändern Sie die Parameterreihenfolge 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, gehen Sie zu Datei > Einstellungen > Editor > Inspektionen und Markieren Sie die Regeln, die Sie unter Kotlin-Interoperabilität aktivieren möchten:
Nachdem Sie die gewünschten Regeln ausgewählt haben, 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.