Ce document est un ensemble de règles pour la création d'API publiques en Java et Kotlin. avec l'intention de faire en sorte que le code soit idiomatique lorsqu'il est consommé langue.
Dernière mise à jour: 29/07/2024
Java (pour une consommation en Kotlin)
Pas de mots clés exacts
N'utilisez aucun des mots clés exacts de Kotlin comme nom de méthode. ou champs. Ceux-ci nécessitent l’utilisation d’accents graves pour s’échapper lors de l’appel depuis Kotlin. Mots clés génériques, mots clés modificateurs et Les identifiants spéciaux sont autorisés.
Par exemple, la fonction when
de Mockito nécessite des accents graves lorsqu'elle est utilisée depuis Kotlin:
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
Éviter les noms d'extension Any
Évitez d'utiliser les noms des fonctions d'extension sur Any
pour
ou les noms des propriétés d'extension sur Any
pour
sauf en cas d'absolue nécessité. Les méthodes et champs des membres
ont priorité sur les fonctions ou propriétés d'extension de Any
, elles peuvent être
lors de la lecture du code,
il est difficile de savoir lequel est appelé.
Annotations de possibilité de valeur nulle
Tout type de paramètre, de retour et de champ non primitif dans une API publique doit une annotation de possibilité de valeur nulle. Les types non annotés sont interprétés comme "plate-forme" , qui présentent une possibilité de valeur nulle ambiguë.
Par défaut, le compilateur Kotlin respecte les annotations JSR 305, mais les signale avec des avertissements. Vous pouvez également définir un indicateur pour que le compilateur traite les annotations comme des erreurs.
Paramètres lambda en dernier
Les types de paramètres éligibles à la conversion SAM doivent être situés en dernier.
Par exemple, la signature de la méthode Flowable.create()
de RxJava 2 est définie comme suit:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Comme FlowableOnSubscriber est éligible à la conversion SAM, les appels de fonction de cette méthode à partir de Kotlin se présente comme suit:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Toutefois, si les paramètres ont été inversés dans la signature de la méthode, les appels de fonction pourrait utiliser la syntaxe du lambda de fin:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Préfixes de propriété
Pour qu'une méthode soit représentée sous la forme d'une propriété en Kotlin, un attribut strict de type "bean" doit être utilisé.
Les méthodes d'accesseur nécessitent un préfixe get
ou, pour les méthodes renvoyant une valeur booléenne, un is
.
public final class User {
public String getName() { /* … */ }
public boolean isActive() { /* … */ }
}
val name = user.name // Invokes user.getName()
val active = user.isActive // Invokes user.isActive()
Les méthodes de mutateur associées nécessitent un préfixe 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)
Si vous souhaitez que les méthodes soient exposées en tant que propriétés, n'utilisez pas de préfixes non standards tels que
Accesseurs has
, set
ou sans préfixe get
. Méthodes avec des préfixes non standards
peuvent toujours être appelés en tant que fonctions, ce qui peut être acceptable en fonction de la
le comportement de la méthode.
Surcharge de l'opérateur
Faites attention aux noms de méthodes qui autorisent une syntaxe de site d'appel spéciale (comme surcharge de l'opérateur en Kotlin). Assurez-vous que les méthodes s'appellent qu'il est judicieux de l'utiliser avec la syntaxe raccourcie.
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 (pour une consommation en Java)
Nom du fichier
Lorsqu'un fichier contient des fonctions ou des propriétés de niveau supérieur, il est toujours annoté
avec @file:JvmName("Foo")
pour lui donner un joli nom.
Par défaut, les membres de premier niveau d'un fichier MyClass.kt se retrouvent dans une classe appelée
MyClassKt
, qui n'est pas attrayant et qui divulgue la langue lors de l'implémentation
dans les moindres détails.
Envisagez d'ajouter @file:JvmMultifileClass
pour combiner les membres de niveau supérieur à partir desquels
plusieurs fichiers dans une seule classe.
Arguments lambda
Les interfaces à méthode unique (SAM, Single Method Interface) définies en Java peuvent être implémentées à la fois en Kotlin et Java à l'aide de la syntaxe lambda, qui intègre l'implémentation dans un langage de la même façon. Kotlin propose plusieurs options pour définir ces interfaces, chacune ayant une légère la différence.
Définition à privilégier
Fonctions d'ordre supérieur destinées à être utilisées à partir de Java
Les types de fonction qui renvoient Unit
ne doivent pas être acceptés
nécessitent que les appelants Java renvoient Unit.INSTANCE
. Au lieu d'intégrer la fonction
saisissez la signature, utilisez des interfaces fonctionnelles (SAM). Aussi
Envisagez d'utiliser des interfaces fonctionnelles (SAM) plutôt que des
interfaces lors de la définition d'interfaces censées être utilisées en tant que lambdas,
ce qui permet une utilisation idiomatique de Kotlin.
Prenons cette définition de Kotlin :
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
En cas d'appel depuis Kotlin :
sayHi { println("Hello, $it!") }
En cas d'appel depuis Java :
sayHi(name -> System.out.println("Hello, " + name + "!"));
Même si le type de fonction ne renvoie pas de Unit
, il peut être judicieux d'en faire une interface nommée pour permettre aux appelants de l'implémenter avec une classe nommée et pas seulement des lambdas (à la fois dans Kotlin, et Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Éviter les types de fonction qui renvoient Unit
Prenons cette définition de Kotlin :
fun sayHi(greeter: (String) -> Unit) = /* … */
Les appelants Java doivent renvoyer Unit.INSTANCE
:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
Éviter les interfaces fonctionnelles lorsque l'implémentation est censée comporter un état
Lorsque l'implémentation de l'interface est censée avoir un état, l'utilisation de la syntaxe lambda n'est pas judicieuse. Comparable en est un bon exemple.
car il est destiné à comparer this
à other
, et les lambdas n'ont pas de this
. Non
le préfixe fun
de l'interface oblige l'appelant à utiliser object : ...
qui lui permet d'avoir un état et de fournir un indice à l'appelant.
Prenons cette définition de Kotlin :
// No "fun" prefix.
interface Counter {
fun increment()
}
Elle empêche la syntaxe lambda en Kotlin, ce qui nécessite cette version plus longue :
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Éviter les génériques Nothing
Les types dont le paramètre générique est Nothing
sont exposés en tant que types bruts en Java. Brut
sont rarement utilisés en Java et doivent être évités.
Documenter les exceptions
Les fonctions qui peuvent générer des exceptions vérifiées doivent les documenter avec @Throws
. Les exceptions d'exécution doivent être documentées dans KDoc.
Faites attention aux API auxquelles une fonction délègue, car elles peuvent générer des exceptions vérifiées que Kotlin peut sinon diffuser en silence.
Copies défensives
Lorsque vous renvoyez des collections partagées ou dont vous n'êtes pas propriétaire en lecture seule à partir d'API publiques, encapsulez dans un conteneur non modifiable ou d’effectuer une copie défensive. Malgré Kotlin leur propriété en lecture seule, cette mesure n'est pas appliquée sur le côté. Sans le wrapper ou la copie défensive, les règles invariantes peuvent être enfreintes par en renvoyant une référence de collection de longue durée.
Fonctions des compagnons
Les fonctions publiques d'un objet compagnon doivent être annotées avec @JvmStatic
.
sous la forme d'une méthode statique.
Sans l'annotation, ces fonctions ne sont disponibles que comme méthodes d'instance
sur un champ Companion
statique.
Incorrect : aucune annotation
class KotlinClass {
companion object {
fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.Companion.doWork();
}
}
Correct : annotation @JvmStatic
class KotlinClass {
companion object {
@JvmStatic fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.doWork();
}
}
Constantes des compagnons
Les propriétés publiques non const
qui sont des constantes effectives dans un companion
object
doivent être annotées avec @JvmField
pour être exposées en tant que champ statique.
Sans l'annotation, ces propriétés sont uniquement disponibles
d'instance "getters" sur le champ statique Companion
. Utilisation de @JvmStatic
à la place
de @JvmField
déplace les "getters" au nom étrange aux méthodes statiques de la classe,
ce qui est toujours incorrect.
Incorrect : aucune annotation
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());
}
}
Incorrect : annotation @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());
}
}
Correct : annotation @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);
}
}
Noms idiomatiques
Kotlin utilise des conventions d'appel différentes de celles de Java, qui peuvent modifier la façon dont vous nommez les fonctions. Utilisez @JvmName
pour créer des noms idiomatiques
pour les conventions des deux langages ou pour correspondre à leur bibliothèque standard respective
nommage.
Cela se produit le plus souvent pour les fonctions et les propriétés d'extension. car l'emplacement du type de récepteur est différent.
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);
}
Surcharges de fonctions pour les valeurs par défaut
Les fonctions avec des paramètres ayant une valeur par défaut doivent utiliser @JvmOverloads
.
Sans cette annotation, il est impossible d'appeler la fonction à l'aide de valeurs par défaut.
Lorsque vous utilisez @JvmOverloads
, inspectez les méthodes générées pour vous assurer qu'elles
qui ont du sens. Si ce n'est pas le cas, effectuez au moins l'une des refactorisations suivantes
jusqu'à satisfaction:
- Modifiez l'ordre des paramètres pour que ceux ayant des valeurs par défaut soient placés en priorité à la fin.
- Déplacez les valeurs par défaut vers des surcharges de fonctions manuelles.
Incorrect : pas de @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");
}
}
Correct : annotation @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");
}
}
Vérifications lint
Conditions requises
- Android Studio 3.2 Canary 10 ou version ultérieure
- Plug-in Android Gradle 3.2 ou version ultérieure
Vérifications prises en charge
Des vérifications Android Lint vous permettent désormais de détecter et de signaler certains des les problèmes d'interopérabilité décrits précédemment. Uniquement les problèmes en Java (pour Kotlin) de consommation) sont détectés. Plus précisément, les vérifications prises en charge sont les suivantes :
- Nullité inconnue
- Accès aux propriétés
- Aucun mot clé exact Kotlin
- Paramètres lambda en dernier
Android Studio
Pour activer ces vérifications, accédez à Fichier > Préférences > Éditeur > les inspections et Cochez les règles que vous souhaitez activer sous Interopérabilité Kotlin:
Figure 1 : Paramètres d'interopérabilité Kotlin dans Android Studio.
Une fois que vous avez coché les règles que vous souhaitez activer, les nouvelles vérifications s'exécuter lorsque vous lancez vos inspections de code (Analyze > Inspect Code... (Analyser > Inspecter le code...))
Builds de ligne de commande
Pour activer ces vérifications à partir des builds de ligne de commande, ajoutez la ligne suivante dans
votre fichier build.gradle
:
Groovy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Pour obtenir la liste complète des configurations compatibles avec lintOptions, consultez la documentation de référence DSL Gradle pour Android.
Ensuite, exécutez ./gradlew lint
à partir de la ligne de commande.