Questo documento è un insieme di regole per la creazione di API pubbliche in Java e Kotlin, allo scopo di fare in modo che il codice risulti idiomatico se utilizzato dall'altro linguaggio.
Ultimo aggiornamento: 18-05-2018
Java (per il consumo di Kotlin)
Nessuna parola chiave rigida
Non utilizzare nessuna parola chiave rigida di Kotlin come nome di metodi o campi. Questi richiedono l'uso di apici inversi per l'escape quando chiami da Kotlin. Sono consentiti parole chiave flessibili, parole chiave di modifica e identificatori speciali.
Ad esempio, la funzione when
di Mockito richiede un apice inverso quando viene utilizzata da Kotlin:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Evita Any
nomi di estensioni
Evita di utilizzare i nomi delle funzioni di estensione su Any
per i metodi o i nomi delle proprietà delle estensioni su Any
per i campi, a meno che non sia assolutamente necessario. Sebbene i metodi e i campi dei membri abbiano sempre la precedenza sulle funzioni o sulle proprietà dell'estensione di Any
, sapere quale viene chiamato quando si legge il codice può essere difficile.
Annotazioni per l'abilità di valori null
Ogni parametro, ritorno e tipo di campo non primitivi in un'API pubblica deve avere un'annotazione con valore nulla. I tipi non annotati vengono interpretati come tipi"platform", con valori ambigui dei valori null.
Per impostazione predefinita, i flag del compilatore Kotlin rispettano le annotazioni JSR 305, ma li segnala con avvisi. Puoi anche impostare un flag in modo che il compilatore consideri le annotazioni come errori.
Ultimi parametri lambda
I tipi di parametri idonei per la conversione SAM devono essere gli ultimi.
Ad esempio, la firma del metodo RxJava 2’s Flowable.create()
è definita come:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Poiché FlowableOnAbbonati è idoneo per la conversione SAM, le chiamate di funzione di questo metodo da Kotlin hanno il seguente aspetto:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Se i parametri fossero invertiti nella firma del metodo, tuttavia, le chiamate di funzione potrebbero utilizzare la sintassi lambda finale:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefissi proprietà
Affinché un metodo venga rappresentato come proprietà in Kotlin, è necessario utilizzare un prefisso rigoroso di tipo "bean".
I metodi della funzione di accesso richiedono un prefisso "get" oppure, per i metodi con restituzione booleana, è possibile utilizzare un prefisso "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()
I metodi mutatori associati richiedono un prefisso "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)
Se vuoi che i metodi siano visualizzati come proprietà, non utilizzare prefissi non standard come "has"/"set" o le funzioni di accesso senza prefisso "get". I metodi con prefissi non standard sono comunque richiamabili come funzioni che possono essere accettabili a seconda del comportamento del metodo.
Sovraccarico dell'operatore
Fai attenzione ai nomi dei metodi che consentono una sintassi speciale del sito di chiamata (ovvero, sovraccarico dell'operatore) in Kotlin. Assicurati che i nomi dei metodi come tali abbiano senso utilizzare con una sintassi abbreviata.
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 (per l'utilizzo di Java)
Nome file
Quando un file contiene funzioni o proprietà di primo livello, aggiungi sempre un'annotazione con @file:JvmName("Foo")
per fornire un nome accattivante.
Per impostazione predefinita, i membri di primo livello in un file MyClass.kt finiranno in una classe denominata
MyClassKt
che è sgradevole e trapela la lingua come dettaglio di implementazione.
Valuta la possibilità di aggiungere @file:JvmMultifileClass
per combinare i membri di primo livello provenienti da più file in un unico corso.
Argomenti lambda
Le interfacce a metodo singolo (SAM) definite in Java possono essere implementate sia in Kotlin che in Java utilizzando la sintassi lambda, che integra l'implementazione in modo idiomatico. Kotlin offre diverse opzioni per la definizione di queste interfacce, ognuna con una leggera differenza.
Definizione preferita
Le funzioni di ordine superiore destinate all'uso da Java non devono accettare i tipi di funzione che restituiscono Unit
, poiché questo richiederebbe ai chiamanti Java di restituire Unit.INSTANCE
.
Anziché incorporare il tipo di funzione nella firma, utilizza le interfacce funzionali (SAM).
Prendi in considerazione l'utilizzo di interfacce funzionali (SAM) anziché normali quando definisci quelle che dovrebbero essere utilizzate come lambda, il che consente un utilizzo idiomatico da Kotlin.
Considera questa definizione di Kotlin:
fun interface GreeterCallback { fun greetName(String name) } fun sayHi(greeter: GreeterCallback) = /* … */
Quando richiamato da Kotlin:
sayHi { println("Hello, $it!") }
Quando richiamato da Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Anche quando il tipo di funzione non restituisce Unit
, potrebbe essere comunque una buona idea creare un'interfaccia con nome per consentire ai chiamanti di implementarla con una classe con nome e non solo con lambdas (sia in Kotlin che in Java).
class MyGreeterCallback : GreeterCallback { override fun greetName(name: String) { println("Hello, $name!"); } }
Evita i tipi di funzione che restituiscono Unit
Considera questa definizione di Kotlin:
fun sayHi(greeter: (String) -> Unit) = /* … */
Richiede che i chiamanti Java restituiscano Unit.INSTANCE
:
sayHi(name -> { System.out.println("Hello, " + name + "!"); return Unit.INSTANCE; });
Evita le interfacce funzionali quando l'implementazione deve avere uno stato
Quando l'implementazione dell'interfaccia deve avere uno stato, l'utilizzo della sintassi lambda non ha senso.
Comparabile
è un esempio evidente, in quanto intende confrontare this
con other
, mentre le lambda
non hanno this
. Se non anteponi il prefisso fun
all'interfaccia, il chiamante può utilizzare la sintassi object : ...
, che può avere lo stato, fornendo un suggerimento al chiamante.
Considera questa definizione di Kotlin:
// No "fun" prefix. interface Counter { fun increment() }
Impedisce la sintassi lambda in Kotlin, richiedendo questa versione più lunga:
runCounter(object : Counter { private var increments = 0 // State override fun increment() { increments++ } })
Evita Nothing
termini generici
Un tipo il cui parametro generico è Nothing
è esposto come tipi non elaborati a Java. I tipi non elaborati vengono utilizzati raramente in Java e devono essere evitati.
Eccezioni per i documenti
Le funzioni che possono generare eccezioni selezionate devono documentarle con
@Throws
. Le eccezioni di runtime devono essere documentate in KDoc.
Fai attenzione alle API a cui delega una funzione, in quanto potrebbero generare eccezioni selezionate che Kotlin altrimenti consente di propagare in modo invisibile all'utente.
Copie difensive
Quando restituisci raccolte di sola lettura condivise o senza proprietà da API pubbliche, includile in un container non modificabile o esegui una copia difensiva. Nonostante Kotlin abbia applicato la sua proprietà di sola lettura, non esiste un'applicazione di questo tipo sul lato Java. Senza il wrapper o il testo difensivo, le informazioni invarianti possono essere violate restituendo un riferimento alla raccolta di lunga durata.
Funzioni di associazione
Le funzioni pubbliche in un oggetto companion devono essere annotate con @JvmStatic
per essere esposte come metodo statico.
Senza l'annotazione, queste funzioni sono disponibili solo come metodi di istanza in un campo Companion
statico.
Risposta sbagliata: nessuna annotazione
class KotlinClass { companion object { fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.Companion.doWork(); } }
Risposta corretta: @JvmStatic
annotazione
class KotlinClass { companion object { @JvmStatic fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.doWork(); } }
Costanti companion
Le proprietà pubbliche non const
che sono costanti efficaci in un companion object
devono essere annotate con @JvmField
per essere esposte come campo statico.
Senza l'annotazione, queste proprietà sono disponibili solo come "getter" di istanze con nomi strani nel campo Companion
statico. L'utilizzo di @JvmStatic
anziché @JvmField
sposta i "getter" con nomi strani nei metodi statici sulla classe, il che non è ancora corretto.
Risposta sbagliata: nessuna annotazione
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()); } }
Non corretto: @JvmStatic
annotazione
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()); } }
Risposta corretta: @JvmField
annotazione
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); } }
Denominazione idiomatica
Kotlin ha convenzioni di chiamata diverse rispetto a Java, che possono cambiare il modo in cui assegni i nomi alle funzioni. Utilizza @JvmName
per progettare nomi in modo che sembrino idiomatici secondo le convenzioni di entrambe le lingue o che corrispondano ai rispettivi nomi standard delle librerie.
Questo problema si verifica più spesso per le funzioni e le proprietà delle estensioni perché la posizione del tipo di destinatario è diversa.
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); }
Sovraccarico delle funzioni per i valori predefiniti
Le funzioni con parametri che hanno un valore predefinito devono utilizzare @JvmOverloads
.
Senza questa annotazione è impossibile richiamare la funzione utilizzando valori predefiniti.
Quando utilizzi @JvmOverloads
, esamina i metodi generati per assicurarti
che ciascuno abbia senso. In caso contrario, esegui uno o entrambi i seguenti refactoring fino a quando non soddisfi il requisito:
- Modifica l'ordine dei parametri per scegliere quelli con valori predefiniti che si avvicinano alla fine.
- Sposta i valori predefiniti in sovraccarichi di funzioni manuali.
Risposta sbagliata: no @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"); } }
Risposta corretta: @JvmOverloads
annotazione.
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"); } }
Controlli lint
Requisiti
- Versione di Android Studio: 3.2 Canary 10 o versioni successive
- Versione del plug-in Android per Gradle: 3.2 o versioni successive
Controlli supportati
Ora ci sono controlli Lint di Android che ti aiuteranno a rilevare e segnalare alcuni dei problemi di interoperabilità descritti in precedenza. Al momento vengono rilevati solo i problemi in Java (per l'utilizzo di Kotlin). In particolare, i controlli supportati sono:
- Nullo sconosciuto
- Accesso alla proprietà
- Nessuna parola chiave hard Kotlin
- Ultimi parametri lambda
Android Studio
Per attivare questi controlli, vai a File > Preferenze > Editor > Ispezioni e seleziona le regole che vuoi attivare in Kotlin Interoperability:
Una volta selezionate le regole da attivare, i nuovi controlli verranno eseguiti quando esegui le ispezioni del codice (Analizza > Ispeziona codice...)
Build dalla riga di comando
Per abilitare questi controlli dalle build a riga di comando, aggiungi la seguente riga al file build.gradle
:
Trendy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Per l'insieme completo delle configurazioni supportate all'interno di lintOptions, consulta la documentazione di riferimento su Gradle DSL per Android.
Quindi, esegui ./gradlew lint
dalla riga di comando.