Questo documento è un insieme di regole per la creazione di API pubbliche in Java e Kotlin con l'intento che il codice risulti idiomatico quando viene utilizzato dall'altro linguaggio.
Java (per l'utilizzo di Kotlin)
Nessuna parola chiave esatta
Non utilizzare nessuna delle parole chiave rigide di Kotlin come nome di metodi o campi. Questi richiedono l'uso di backtick per l'escape quando vengono chiamati da Kotlin. Sono consentite le parole chiave soft, le parole chiave modificatore e gli identificatori speciali.
Ad esempio, la funzione when di Mockito richiede i backtick quando viene utilizzata da Kotlin:
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
Evita i nomi delle estensioni Any
Evita di utilizzare i nomi delle funzioni di estensione su Any per
metodi o i nomi delle proprietà di estensione su Any per
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à di estensione di Any, può essere difficile capire quale viene chiamato durante la lettura del codice.
Annotazioni di annullabilità
Ogni parametro, valore restituito e tipo di campo non primitivo in un'API pubblica deve avere un'annotazione di nullabilità. I tipi non annotati vengono interpretati come tipi"platform", che hanno una nullabilità ambigua.
Per impostazione predefinita, il compilatore Kotlin rispetta le annotazioni JSR 305, ma le segnala con avvisi. Puoi anche impostare un flag per fare in modo che il compilatore tratti le annotazioni come errori.
Parametri lambda per ultimi
I tipi di parametri idonei per la conversione SAM devono essere gli ultimi.
Ad esempio, la firma del metodo Flowable.create() di RxJava 2 è definita come:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Poiché FlowableOnSubscribe è 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, le chiamate di funzione potrebbero utilizzare la sintassi lambda finale:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefissi delle proprietà
Affinché un metodo venga rappresentato come proprietà in Kotlin, è necessario utilizzare un prefisso rigoroso in stile "bean".
I metodi di accesso richiedono un prefisso get o, per i metodi che restituiscono valori booleani, è 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 vengano esposti come proprietà, non utilizzare prefissi non standard come
has, set o funzioni di accesso senza prefisso get. I metodi con prefissi non standard
sono comunque richiamabili come funzioni, il che potrebbe essere accettabile a seconda del
comportamento del metodo.
Sovraccarico dell'operatore
Presta attenzione ai nomi dei metodi che consentono una sintassi speciale di chiamata al sito (ad esempio l'overload degli operatori in Kotlin). Assicurati che i nomi dei metodi 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 l'utilizzo di Java)
Nome file
Quando un file contiene funzioni o proprietà di primo livello, annotalo sempre con @file:JvmName("Foo") per fornire un nome descrittivo.
Per impostazione predefinita, i membri di primo livello in un file MyClass.kt finiscono in una classe chiamata
MyClassKt, il che non è piacevole e rivela il linguaggio come dettaglio di implementazione.
Valuta la possibilità di aggiungere @file:JvmMultifileClass per combinare i membri di primo livello di
più file in un'unica classe.
Argomenti lambda
Le interfacce con un solo metodo (SAM) definite in Java possono essere implementate sia in Kotlin che in Java utilizzando la sintassi lambda, che incorpora l'implementazione in modo idiomatico. Kotlin offre diverse opzioni per definire queste interfacce, ognuna con una leggera differenza.
Definizione preferibile
Le funzioni di ordine superiore destinate all'uso da Java
non devono accettare tipi di funzioni che restituiscono Unit, in quanto ciò
richiederebbe ai chiamanti Java di restituire Unit.INSTANCE. Anziché incorporare il tipo di funzione nella firma, utilizza le interfacce funzionali (SAM). Inoltre,
valuta la possibilità di utilizzare interfacce funzionali (SAM) anziché interfacce
normali quando definisci interfacce che devono essere utilizzate come espressioni lambda,
il che consente un utilizzo idiomatico da Kotlin.
Considera questa definizione Kotlin:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Se richiamato da Kotlin:
sayHi { println("Hello, $it!") }
Quando viene richiamato da Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Anche quando il tipo di funzione non restituisce un valore Unit, potrebbe comunque essere una buona idea renderlo un'interfaccia con nome per consentire ai chiamanti di implementarlo con una classe con nome e non solo con espressioni lambda (sia in Kotlin che in Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Evita i tipi di funzioni che restituiscono Unit
Considera questa definizione Kotlin:
fun sayHi(greeter: (String) -> Unit) = /* … */
Richiede ai chiamanti Java di restituire 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. Comparable è un esempio importante,
in quanto è progettato per confrontare this con other e le espressioni lambda non hanno this. Il mancato
prefisso dell'interfaccia con fun costringe il chiamante a utilizzare la sintassi object : ..., che gli consente di avere uno stato, fornendo un suggerimento al chiamante.
Considera questa definizione 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 i farmaci generici Nothing
Un tipo il cui parametro generico è Nothing viene esposto come tipi non elaborati a Java. I tipi
non elaborati vengono utilizzati raramente in Java e devono essere evitati.
Eccezioni relative ai documenti
Le funzioni che possono generare eccezioni controllate devono documentarle con @Throws. Le eccezioni di runtime devono essere documentate in KDoc.
Presta attenzione alle API a cui una funzione delega, in quanto potrebbero generare eccezioni controllate che Kotlin altrimenti consente di propagare in modo silenzioso.
Copie difensive
Quando restituisci raccolte di sola lettura condivise o non di proprietà da API pubbliche, racchiudile in un contenitore non modificabile o esegui una copia difensiva. Nonostante Kotlin imponga la proprietà di sola lettura, non esiste alcuna applicazione lato Java. Senza il wrapper o la copia difensiva, gli invarianti possono essere violati restituendo un riferimento a una raccolta di lunga durata.
Funzioni complementari
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.
Errato: 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 e non const che sono costanti effettive 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 statico Companion. L'utilizzo di @JvmStatic anziché di @JvmField sposta i "getter" con nomi strani nei metodi statici della classe, il che è comunque errato.
Errato: 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());
}
}
Errato: @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);
}
}
Naming idiomatico
Kotlin ha convenzioni di chiamata diverse da Java, il che può cambiare il modo in cui
nomini le funzioni. Utilizza @JvmName per progettare nomi che risultino idiomatici
per le convenzioni di entrambe le lingue o per corrispondere alla denominazione
della rispettiva libreria standard.
Ciò si verifica più frequentemente per le funzioni di estensione e le proprietà di estensione perché la posizione del tipo di destinatario è diversa.
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);
}
Overload di 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 ognuno
abbia senso. In caso contrario, esegui uno o entrambi i seguenti refactoring
finché non sei soddisfatto:
- Modifica l'ordine dei parametri in modo da preferire quelli con valori predefiniti verso la fine.
- Sposta i valori predefiniti negli overload manuali delle funzioni.
Errato: 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 lint
Requisiti
- Versione di Android Studio:3.2 Canary 10 o versioni successive
- Versione del plug-in Android per Gradle:3.2 o successive
Controlli supportati
Ora sono disponibili controlli Android Lint che ti aiuteranno a rilevare e segnalare alcuni dei problemi di interoperabilità descritti in precedenza. Vengono rilevati solo i problemi in Java (per l'utilizzo di Kotlin). Nello specifico, i controlli supportati sono:
- Unknown Nullness
- Accesso alla proprietà
- Nessuna parola chiave Kotlin Hard
- Lambda Parameters Last
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.
Una volta selezionate le regole che vuoi attivare, i nuovi controlli verranno eseguiti quando esegui le ispezioni del codice (Analizza > Ispeziona codice…).
Build da riga di comando
Per abilitare questi controlli dalle build della riga di comando, aggiungi la seguente riga nel file build.gradle:
Alla moda
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Per l'insieme completo di configurazioni supportate all'interno di lintOptions, consulta la documentazione di riferimento di Gradle DSL di Android.
Quindi, esegui ./gradlew lint dalla riga di comando.