Questo documento è un insieme di regole per la creazione di API pubbliche in Java e Kotlin con l'intento di far sembrare il codice idiomatico se utilizzato dall'altro linguaggio.
Ultimo aggiornamento: 18/05/2018
Java (per il consumo di Kotlin)
Nessuna parola chiave complessa
Non utilizzare nessuna delle parole chiave hard di Kotlin come nome di metodi o campi. Questi ultimi richiedono l'uso di zaini per poter uscire da Kotlin. Sono consentite le parole chiave soft, le parole chiave con modificatori e gli identificatori speciali.
Ad esempio, la funzione when
di Mockito richiede il backtick quando viene utilizzata da Kotlin:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Evita nomi di estensioni Any
Evita di utilizzare i nomi delle funzioni delle estensioni su Any
per i metodi o i nomi delle proprietà delle estensioni su Any
per i campi a meno che assolutamente necessario. Sebbene i metodi e i campi dei membri abbiano sempre la precedenza sulle funzioni o proprietà delle estensioni di Any
, può essere difficile leggere il codice per sapere quale viene richiamato.
Annotazioni nullità
Ogni parametro, elemento restituito e tipo di campo non primitivo in un'API pubblica deve avere un'annotazione nullità. I tipi non annotati sono interpretati come tipi di"piattaforma", con nullità ambigua.
Per impostazione predefinita, il flag di compilazione di Kotlin rispetta le annotazioni JSR 305, ma li segnala con avvisi. Puoi anche impostare un flag per fare in modo che il compilatore tratti 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()
viene definita come:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Poiché FlowableOnSubscribe è idoneo per la conversione SAM, le chiamate alle funzioni di questo metodo provenienti da Kotlin hanno il seguente aspetto:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Se i parametri sono stati invertiti nella firma del metodo, tuttavia, le chiamate funzione potrebbero utilizzare la sintassi finale-lambda:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefissi proprietà
Affinché un metodo sia rappresentato come una proprietà in Kotlin, deve essere utilizzato un prefisso preciso in stile "bean".
I metodi della funzione di accesso richiedono un prefisso "get" o per i metodi di ritorno booleani può essere utilizzato 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 dei 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 vengano mostrati come proprietà, non utilizzare prefissi non standard come le funzioni di accesso con prefisso "has"/'set' o non "get". I metodi con prefissi non standard sono comunque richiamabili come funzioni che possono essere accettabili a seconda del comportamento del metodo.
Sovraccarico operatore
Fai attenzione ai nomi dei metodi che consentono la sintassi speciale del sito di chiamata (ad es. sovraccarico dell'operatore) in Kotlin. Assicurati che i nomi dei metodi come tali abbiano senso da utilizzare con la 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 il consumo di Java)
Nome file
Quando un file contiene funzioni o proprietà di primo livello, annotalo sempre con @file:JvmName("Foo")
per specificare un nome accattivante.
Per impostazione predefinita, i membri di primo livello di un file MyClass.kt finiranno in una classe chiamata
MyClassKt
, che non è gradevole e presenta la lingua come dettaglio dell'implementazione.
Valuta la possibilità di aggiungere @file:JvmMultifileClass
per combinare i membri di primo livello di più file in un unico corso.
Argomenti lambda
I tipi di funzione
che devono essere utilizzati da Java devono evitare il tipo di ritorno
Unit
. A tale scopo, è necessario specificare un'affermazione esplicita di return
Unit.INSTANCE;
che sia in modo idiomatico.
fun sayHi(callback: (String) -> Unit) = /* … */
// Kotlin caller: greeter.sayHi { Log.d("Greeting", "Hello, $it!") }
// Java caller: greeter.sayHi(name -> { Log.d("Greeting", "Hello, " + name + "!"); return Unit.INSTANCE; });
Inoltre, questa sintassi non consente di fornire un tipo con nome semantico in modo da poter essere implementato su altri tipi.
La definizione di un'interfaccia con metodo singolo e astratto (SAM) in Kotlin per il tipo lambda corregge il problema di Java, ma impedisce l'uso di sintassi lambda in Kotlin.
interface GreeterCallback { fun greetName(name: String): Unit } fun sayHi(callback: GreeterCallback) = /* … */
// Kotlin caller: greeter.sayHi(object : GreeterCallback { override fun greetName(name: String) { Log.d("Greeting", "Hello, $name!") } })
// Java caller: greeter.sayHi(name -> Log.d("Greeting", "Hello, " + name + "!"))
La definizione di un'interfaccia SAM con nome in Java consente di utilizzare una versione leggermente inferiore della sintassi lambda Kotlin, in cui il tipo di interfaccia deve essere specificato in modo esplicito.
// Defined in Java: interface GreeterCallback { void greetName(String name); }
fun sayHi(greeter: GreeterCallback) = /* … */
// Kotlin caller: greeter.sayHi(GreeterCallback { Log.d("Greeting", "Hello, $it!") })
// Java caller: greeter.sayHi(name -> Log.d("Greeter", "Hello, " + name + "!"));
Al momento non è possibile definire un tipo di parametro da utilizzare come lambda di Java e Kotlin, in modo da sembrare idiomatico da entrambi i linguaggi. Il consiglio attuale è quello di preferire il tipo di funzione nonostante l'esperienza con Java sia ridotta quando il tipo di ritorno è Unit
.
Evita i generici Nothing
Un tipo il cui parametro generico è Nothing
viene mostrato come tipo non elaborato a Java. I tipi non elaborati vengono utilizzati raramente in Java e devono essere evitati.
Eccezioni documento
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 una delega una funzione perché potrebbero generare eccezioni selezionate che Kotlin consente altrimenti di propagare in silenzio.
Copie difensive
Quando restituisci raccolte di sola lettura condivise o non di proprietà dalle API pubbliche, inseriscile in un container non modificabile o esegui una copia difensiva. Nonostante Kotlin ne abbia applicato la proprietà di sola lettura, questa applicazione non viene eseguita dal lato Java. Senza il wrapper o il testo difensivo, le varianti potrebbero essere violate restituendo un riferimento di raccolta di lunga durata.
Funzioni companion
Le funzioni pubbliche in un oggetto companion devono avere un'annotazione @JvmStatic
per essere esposte come metodo statico.
Senza l'annotazione, queste funzioni sono disponibili solo come metodi di istanza in un campo statico Companion
.
Non corretto: nessuna annotazione
class KotlinClass { companion object { fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.Companion.doWork(); } }
Corretto: @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 elemento companion object
devono avere annotazioni con @JvmField
per essere esposte come campo statico.
Senza l'annotazione, queste proprietà sono disponibili soltanto come istanza con nome strano "getters" nel campo statico Companion
. L'utilizzo di @JvmStatic
invece di
@JvmField
sposta gli "inseritori" dai nomi strani nei metodi statici della classe, che non sono ancora corretti.
Non corretto: 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()); } }
Corretto: @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 modificare il modo in cui
denomina le funzioni. Usa @JvmName
per progettare i nomi in modo che sembrino idiomatici, per entrambe le convenzioni linguistiche o per la loro denominazione standard.
Questo accade più spesso per le funzioni e le proprietà delle estensioni perché la posizione del tipo di ricevitore è 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); }
Sovraccari per le funzioni per impostazione predefinita
Le funzioni con parametri che hanno un valore predefinito devono utilizzare @JvmOverloads
.
Senza questa annotazione è impossibile richiamare la funzione utilizzando qualsiasi valore predefinito.
Quando utilizzi @JvmOverloads
, esamina i metodi generati per assicurarti che abbiano senso. In caso contrario, esegui una o entrambe le seguenti operazioni di refactoring fino a quando l'ordine non sarà soddisfacente:
- Modifica l'ordine dei parametri in modo che preferisca quelli con valori predefiniti verso la fine.
- Sposta i valori predefiniti in sovraccarico manuale delle funzioni.
Non corretto: 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"); } }
Corretto: @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 per pelucchi
Requisiti
- Android Studio:3.2 Canary 10 o versioni successive
- Plug-in Android per Gradle:3.2 o versioni successive
Controlli supportati
Ora sono disponibili controlli Android Lint che ti aiuteranno a rilevare e segnalare alcuni dei problemi di interoperabilità descritti sopra. Attualmente vengono rilevati solo i problemi in Java (per il consumo di Kotlin). Nello specifico, i controlli supportati sono:
- Valore null sconosciuto
- Accesso proprietà
- Nessuna parola chiave Hart Kotlin
- Ultimi parametri della funzione lambda
Android Studio
Per attivare questi controlli, vai a File > Preferenze > Editor > Ispezioni e seleziona le regole che vuoi attivare in Interoperabilità Kotlin:
Figura 1. Impostazioni di interoperabilità di Kotlin in Android Studio.
Dopo aver selezionato le regole da attivare, i nuovi controlli verranno eseguiti quando esegui le ispezioni del codice (Analisi > Ispeziona codice...).
Build a riga di comando
Per abilitare questi controlli dalla build della riga di comando, aggiungi la seguente riga nel file build.gradle
:
Scadente
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Per l'intero insieme di configurazioni supportate all'interno di lintOptions, consulta il riferimento DSL di Android.
Quindi, esegui ./gradlew lint
dalla riga di comando.