Questo documento è un insieme di regole per la creazione di API pubbliche in Java e Kotlin con l'intento di far sì che il codice risulti idiomatico se utilizzato dall'altro lingua.
Ultimo aggiornamento: 29-07-2024
Java (per l'utilizzo di Kotlin)
Nessuna parola chiave difficile
Non utilizzare nessuna delle parole chiave hard di Kotlin come nome dei metodi o campi. Richiedono l'uso di accenti inversi come escape durante la chiamata da Kotlin. Parole chiave flessibili, parole chiave modificatori e sono consentiti identificatori speciali.
Ad esempio, la funzione when
di Mockito richiede l'accento grave 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
o i nomi delle proprietà dell'estensione su Any
per
campi, a meno che non siano assolutamente necessari. Sebbene i metodi e i campi dei membri
hanno la precedenza sulle funzioni o proprietà delle estensioni di Any
, possono essere
difficile capire quale viene chiamato durante la lettura del codice.
Annotazioni di nullità
In un'API pubblica, ogni parametro, ritorno e tipo di campo non primitivo deve dispongono di un'annotazione con valore nullo. I tipi non annotati sono interpretati come "piattaforma" type, che hanno un valore nullo ambiguo.
Per impostazione predefinita, i flag del compilatore Kotlin rispettano le annotazioni JSR 305, ma le contrassegnano con gli avvisi. Puoi anche impostare un flag in modo che il compilatore gestisca 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 Flowable.create()
di RxJava 2 è definita come:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Poiché FlowableOnAbbonati è idoneo per la conversione SAM, le chiamate di funzione questo metodo di Kotlin ha il seguente aspetto:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Se i parametri sono stati invertiti nella firma del metodo, però, le chiamate di funzione potresti usare la sintassi trailing-lambda:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefissi proprietà
Affinché un metodo venga rappresentato come proprietà in Kotlin, utilizza rigoroso stile "a fagiolo" è necessario utilizzare un prefisso.
I metodi della funzione di accesso richiedono un prefisso get
oppure, per i metodi con restituzione booleana, 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()
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 esposti come proprietà, non utilizzare prefissi non standard come
Funzioni di accesso con prefisso has
, set
o senza get
. Metodi con prefissi non standard
sono comunque richiamabili come funzioni, il che può essere accettabile a seconda
comportamento del metodo.
Sovraccarico dell'operatore
Presta attenzione ai nomi dei metodi che consentono una sintassi speciale per i siti di chiamata (come sovraccarichi dell'operatore in Kotlin). Assicurati che i metodi denominano come ha senso usare 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 Java)
Nome file
Quando un file contiene funzioni o proprietà di primo livello, annotalo sempre
con @file:JvmName("Foo")
per dare un bel nome.
Per impostazione predefinita, i membri di primo livello in un file MyClass.kt finiranno in una classe chiamata
MyClassKt
, che è sgradevole e fa trapelare il linguaggio utilizzato come implementazione
dettaglio.
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 SAM definite in Java possono essere implementate sia in Kotlin Java usando la sintassi lambda, che allinea l'implementazione in un modo in molti modi diversi. Kotlin ha diverse opzioni per definire queste interfacce, ognuna con una la differenza.
Definizione preferita
Funzioni di ordine superiore destinate all'utilizzo da Java
non deve accettare tipi di funzione che restituiscono Unit
come faresti
richiede che i chiamanti Java restituiscano Unit.INSTANCE
. Invece di incorporare la funzione
digita la firma, utilizza le interfacce funzionali (SAM). Inoltre
Prendi in considerazione l'utilizzo di interfacce funzionali (SAM) invece delle normali
quando si definiscono le interfacce che dovrebbero essere usate come lambda,
che consente l'uso idiomatico da parte di Kotlin.
Considera questa definizione di Kotlin:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Quando la chiamata viene richiamata da Kotlin:
sayHi { println("Hello, $it!") }
Se la richiesta viene richiamata da Java:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Anche se il tipo di funzione non restituisce un Unit
, potrebbe comunque essere una buona
l'idea di trasformarla in un'interfaccia denominata per consentire ai chiamanti di implementarla con un
e non solo lambdas (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 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 interfacce funzionali se l'implementazione deve avere uno stato
Quando l'implementazione dell'interfaccia deve avere uno stato, utilizzando la funzione lambda
la sintassi è senza senso. Comparable è un esempio importante,
perché l'obiettivo è confrontare this
con other
e i lambda non hanno this
. No
l'aggiunta di fun
all'interfaccia impone al chiamante di utilizzare object : ...
che gli consente di avere uno stato e di fornire un suggerimento al chiamante.
Considera questa definizione di Kotlin:
// No "fun" prefix.
interface Counter {
fun increment()
}
Impedisce la sintassi lambda in Kotlin e richiede questa versione più lunga:
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Evita Nothing
di caratteri generici
Un tipo il cui parametro generico è Nothing
viene esposto come tipi non elaborati in Java. Grezzo
vengono utilizzati raramente in Java e devono essere evitati.
Eccezioni relative ai documenti
Le funzioni che possono generare eccezioni selezionate devono documentarle con
@Throws
. Le eccezioni di runtime devono essere documentate in KDoc.
Presta attenzione alle API a cui delega una funzione, che potrebbero restituire eccezioni che Kotlin consente altrimenti di propagare silenziosamente.
Copie difensive
Quando restituisci raccolte di sola lettura condivise o non possedute da API pubbliche, esegui il wrapping in un container non modificabile o eseguire una copia difensiva. Nonostante Kotlin l'applicazione forzata della proprietà di sola lettura, non è prevista lato server. Senza il wrapper o il testo difensivo, le invarianti possono essere violate che restituisce un riferimento a una raccolta di lunga durata.
Funzioni companion
Le funzioni pubbliche in un oggetto companion devono essere annotate con @JvmStatic
da esporre come metodo statico.
Senza l'annotazione, queste funzioni sono disponibili solo come metodi di istanza
in un campo Companion
statico.
Risposta errata: 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 effettive in un companion
object
devono essere annotate con @JvmField
per essere esposte come campo statico.
Senza l'annotazione, queste proprietà sono disponibili solo con nomi strani
istanza "getters" nel campo Companion
statico. Utilizzo di @JvmStatic
di @JvmField
muove i "getter" con nomi strani a metodi statici sulla classe,
che è ancora errato.
Risposta errata: 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: annotazione @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());
}
}
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 cambiare il modo
le funzioni per i nomi. Usa @JvmName
per creare nomi in modo che risultino idiomatici
per entrambe le convenzioni della lingua o per le rispettive librerie standard
di denominazione.
Questo problema si verifica più spesso per le funzioni e le proprietà delle estensioni perché la località 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);
}
Sovraccarico delle funzioni per i valori predefiniti
Le funzioni con parametri che hanno un valore predefinito devono usare @JvmOverloads
.
Senza questa annotazione è impossibile richiamare la funzione utilizzando
i valori predefiniti.
Quando utilizzi @JvmOverloads
, ispeziona i metodi generati per assicurarti che ciascuno
ha senso. In caso contrario, esegui uno o entrambi i seguenti refactoring
fino al completamento:
- Modifica l'ordine dei parametri in modo da preferire quelli con valori predefiniti corrispondenti fine.
- Sposta i valori predefiniti in sovraccarichi di funzioni manuali.
Risposta errata: 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 esatta: annotazione @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");
}
}
Controllo pelucchi
Requisiti
- Android Studio versione:3.2 Canary 10 o versioni successive
- Versione del plug-in Android Gradle: 3.2 o versioni successive
Assegni supportati
Sono ora disponibili controlli Android Lint che ti aiuteranno a rilevare e segnalare alcuni dei sui problemi di interoperabilità descritti in precedenza. Solo problemi in Java (per Kotlin il consumo energetico). In particolare, i controlli supportati sono:
- Nullità sconosciuta
- Accesso alla proprietà
- Nessuna parola chiave hard Kotlin
- Ultimo parametro Lambda
Android Studio
Per attivare questi controlli, vai a File > Preferenze > Editor > Ispezioni e Controlla le regole che vuoi attivare in Kotlin Interoperability:
Una volta selezionate le regole da attivare, i nuovi controlli esegui quando esegui le ispezioni del codice (Analizza > Ispeziona codice...)
Build dalla riga di comando
Per abilitare questi controlli dalle build della riga di comando, aggiungi la riga seguente in
il tuo file build.gradle
:
Alla moda
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Per l'insieme completo delle configurazioni supportate all'interno di lintOptions, fai riferimento alle Riferimento DSL per Android Gradle.
Poi esegui ./gradlew lint
dalla riga di comando.