Ten dokument zawiera zbiór reguł tworzenia publicznych interfejsów API w językach Java i Kotlin, które mają sprawić, że kod będzie idiomatyczny, gdy będzie używany w innym języku.
Java (do użycia w języku Kotlin)
Brak słów kluczowych o dużej konkurencyjności
Nie używaj żadnych słów kluczowych w języku Kotlin jako nazw metod ani pól. Wymagają one użycia apostrofów zwrotnych, aby uniknąć problemów podczas wywoływania z Kotlin. Dozwolone są słowa kluczowe miękkie, słowa kluczowe modyfikujące i identyfikatory specjalne.
Na przykład funkcja when Mockito wymaga użycia apostrofów zwrotnych w Kotlinie:
val callable = Mockito.mock(Callable::class.java)
Mockito.`when`(callable.call()).thenReturn(/* … */)
Unikaj nazw rozszerzeń Any
Unikaj używania nazw funkcji rozszerzających w Any w przypadku metod lub nazw właściwości rozszerzających w Any w przypadku pól, chyba że jest to absolutnie konieczne. Metody i pola elementów zawsze mają pierwszeństwo przed funkcjami lub właściwościami rozszerzeń Any, ale podczas czytania kodu trudno jest stwierdzić, która z nich jest wywoływana.
Adnotacje dotyczące dopuszczalności wartości null
Każdy niepodstawowy parametr, zwracana wartość i typ pola w publicznym interfejsie API powinien mieć adnotację o dopuszczalności wartości null. Typy bez adnotacji są interpretowane jako typy „platform”, które mają niejednoznaczną dopuszczalność wartości null.
Domyślnie kompilator Kotlin rozpoznaje adnotacje JSR 305, ale oznacza je ostrzeżeniami. Możesz też ustawić flagę, aby kompilator traktował adnotacje jako błędy.
Parametry funkcji Lambda na końcu
Typy parametrów kwalifikujące się do konwersji SAM powinny być ostatnie.
Na przykład sygnatura metody Flowable.create() w RxJava 2 jest zdefiniowana w ten sposób:
public static <T> Flowable<T> create(
FlowableOnSubscribe<T> source,
BackpressureStrategy mode) { /* … */ }
Ponieważ FlowableOnSubscribe kwalifikuje się do konwersji SAM, wywołania funkcji tej metody z Kotlina wyglądają tak:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Jeśli jednak parametry zostałyby odwrócone w sygnaturze metody, wywołania funkcji mogłyby używać składni lambda końcowej:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefiksy właściwości
Aby metoda była reprezentowana jako właściwość w Kotlinie, musi być użyty ścisły prefiks w stylu „bean”.
Metody dostępu wymagają prefiksu get lub w przypadku metod zwracających wartość logiczną można użyć prefiksu 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()
Powiązane metody modyfikujące wymagają prefiksu 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)
Jeśli chcesz, aby metody były widoczne jako właściwości, nie używaj niestandardowych prefiksów, takich jak has, set ani akcesorów bez prefiksu get. Metody z niestandardowymi prefiksami nadal można wywoływać jako funkcje, co może być dopuszczalne w zależności od ich działania.
Przeciążanie operatorów
Zwróć uwagę na nazwy metod, które umożliwiają specjalną składnię wywołania (np. przeciążanie operatorów w Kotlinie). Upewnij się, że nazwy metod mają sens w przypadku użycia skróconej składni.
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 (do użycia w języku Java)
Nazwa pliku
Jeśli plik zawiera funkcje lub właściwości najwyższego poziomu, zawsze dodawaj do niego adnotację @file:JvmName("Foo"), aby nadać mu odpowiednią nazwę.
Domyślnie elementy najwyższego poziomu w pliku MyClass.kt trafiają do klasy o nazwieMyClassKt, co jest nieatrakcyjne i ujawnia język jako szczegół implementacji.
Rozważ dodanie @file:JvmMultifileClass, aby połączyć elementy najwyższego poziomu z wielu plików w jedną klasę.
Argumenty funkcji Lambda
Interfejsy z jedną metodą (SAM) zdefiniowane w Javie można implementować w językach Kotlin i Java za pomocą składni lambda, która wstawia implementację w idiomatyczny sposób. Kotlin oferuje kilka opcji definiowania takich interfejsów, z których każda ma niewielkie różnice.
Preferowana definicja
Funkcje wyższego rzędu, które mają być używane w Java, nie powinny przyjmować typów funkcji zwracających Unit, ponieważ wymagałoby to od wywołujących w Java zwracania Unit.INSTANCE. Zamiast wstawiać typ funkcji w sygnaturze, używaj interfejsów funkcyjnych (SAM). Podczas definiowania interfejsów, które mają być używane jako wyrażenia lambda, warto też używać interfejsów funkcyjnych (SAM) zamiast zwykłych interfejsów, co umożliwia idiomatyczne użycie w języku Kotlin.
Weźmy pod uwagę tę definicję w języku Kotlin:
fun interface GreeterCallback {
fun greetName(String name)
}
fun sayHi(greeter: GreeterCallback) = /* … */
Wywołanie z języka Kotlin:
sayHi { println("Hello, $it!") }
Wywołanie z Javy:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Nawet jeśli typ funkcji nie zwraca wartości Unit, warto utworzyć z niego interfejs nazwany, aby umożliwić wywołującym zaimplementowanie go za pomocą klasy nazwanej, a nie tylko wyrażeń lambda (zarówno w języku Kotlin, jak i Java).
class MyGreeterCallback : GreeterCallback {
override fun greetName(name: String) {
println("Hello, $name!");
}
}
Unikaj typów funkcji, które zwracają wartość Unit
Weźmy pod uwagę tę definicję w języku Kotlin:
fun sayHi(greeter: (String) -> Unit) = /* … */
Wymaga to zwrócenia przez wywołujących w Javie wartości Unit.INSTANCE:
sayHi(name -> {
System.out.println("Hello, " + name + "!");
return Unit.INSTANCE;
});
Unikaj interfejsów funkcyjnych, gdy implementacja ma mieć stan
Jeśli implementacja interfejsu ma mieć stan, używanie składni lambda nie ma sensu. Comparable to ważny przykład, ponieważ ma porównywać this z other, a funkcje lambda nie mają this. Brak prefiksu fun w interfejsie wymusza na wywołującym użycie składni object : ..., która umożliwia mu posiadanie stanu, co stanowi wskazówkę dla wywołującego.
Weźmy pod uwagę tę definicję w języku Kotlin:
// No "fun" prefix.
interface Counter {
fun increment()
}
Uniemożliwia używanie składni lambda w języku Kotlin, co wymaga użycia dłuższej wersji:
runCounter(object : Counter {
private var increments = 0 // State
override fun increment() {
increments++
}
})
Unikaj Nothing ogólnych
Typ, którego parametr ogólny to Nothing, jest udostępniany w Javie jako typy surowe. Typy surowe są rzadko używane w Javie i należy ich unikać.
Wyjątki dotyczące dokumentów
Funkcje, które mogą zgłaszać wyjątki sprawdzane, powinny być udokumentowane za pomocą adnotacji @Throws. Wyjątki środowiska wykonawczego powinny być udokumentowane w KDoc.
Zwróć uwagę na interfejsy API, do których funkcja deleguje zadania, ponieważ mogą one zgłaszać wyjątki sprawdzane, które Kotlin w innych przypadkach cicho propaguje.
Kopie ochronne
Gdy zwracasz udostępnione lub niebędące Twoją własnością kolekcje tylko do odczytu z publicznych interfejsów API, umieść je w nieedytowalnym kontenerze lub wykonaj kopię obronną. Mimo że Kotlin wymusza właściwość tylko do odczytu, po stronie Javy nie ma takiego wymuszenia. Bez otoczki lub kopii obronnej niezmienniki mogą zostać naruszone przez zwrócenie odwołania do kolekcji o długim okresie istnienia.
Funkcje towarzyszące
Funkcje publiczne w obiekcie towarzyszącym muszą być oznaczone adnotacją @JvmStatic, aby były widoczne jako metoda statyczna.
Bez adnotacji te funkcje są dostępne tylko jako metody instancji w statycznym polu Companion.
Nieprawidłowe: brak adnotacji
class KotlinClass {
companion object {
fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.Companion.doWork();
}
}
Prawidłowa: @JvmStatic annotation
class KotlinClass {
companion object {
@JvmStatic fun doWork() {
/* … */
}
}
}
public final class JavaClass {
public static void main(String... args) {
KotlinClass.doWork();
}
}
Stałe kreacji towarzyszących
Publiczne właściwości inne niż const, które są stałymi w companion
object, muszą być oznaczone adnotacją @JvmField, aby były widoczne jako pole statyczne.
Bez adnotacji te właściwości są dostępne tylko jako dziwnie nazwane „gettery” instancji w statycznym polu Companion. Użycie @JvmStatic zamiast @JvmField przenosi dziwnie nazwane „gettery” do metod statycznych w klasie, co nadal jest nieprawidłowe.
Nieprawidłowe: brak adnotacji
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());
}
}
Nieprawidłowa: @JvmStatic adnotacja
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());
}
}
Prawidłowa: @JvmField annotation
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);
}
}
Idiomatyczne nazewnictwo
Kotlin ma inne konwencje wywoływania niż Java, co może zmienić sposób nazywania funkcji. Użyj @JvmName, aby zaprojektować nazwy tak, aby były zgodne z konwencjami obu języków lub z nazwami w odpowiednich bibliotekach standardowych.
Najczęściej zdarza się to w przypadku funkcji rozszerzeń i właściwości rozszerzeń, ponieważ lokalizacja typu odbiorcy jest inna.
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);
}
Przeciążenia funkcji dla wartości domyślnych
Funkcje z parametrami o wartości domyślnej muszą używać znaku @JvmOverloads.
Bez tej adnotacji nie można wywołać funkcji przy użyciu żadnych wartości domyślnych.
Gdy używasz @JvmOverloads, sprawdzaj wygenerowane metody, aby upewnić się, że każda z nich ma sens. Jeśli nie, wykonaj co najmniej 1 z tych refaktoryzacji, aż uzyskasz zadowalający efekt:
- Zmień kolejność parametrów, aby te z wartościami domyślnymi były na końcu.
- Przenieś wartości domyślne do ręcznych przeciążeń funkcji.
Nieprawidłowe: nie @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");
}
}
Poprawny zapis: @JvmOverloads annotation.
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");
}
}
Sprawdzanie kodu
Wymagania
- Wersja Android Studio: 3.2 Canary 10 lub nowsza
- Wersja wtyczki Androida do obsługi Gradle: 3.2 lub nowsza
Obsługiwane kontrole
Obecnie dostępne są testy Lint w Androidzie, które pomagają wykrywać i oznaczać niektóre z opisanych wcześniej problemów z współdziałaniem. Wykrywane są tylko problemy w języku Java (na potrzeby języka Kotlin). Obsługiwane testy to:
- Nieznana wartość null
- Dostęp do usługi
- Brak słów kluczowych Hard Kotlin
- Parametry Lambda na końcu
Android Studio
Aby włączyć te kontrole, otwórz Plik > Preferencje > Edytor > Inspekcje i zaznacz reguły, które chcesz włączyć w sekcji Współdziałanie z Kotlinem:

Rysunek 1. Ustawienia interoperacyjności Kotlin w Androidzie Studio.
Po zaznaczeniu reguł, które chcesz włączyć, nowe testy będą przeprowadzane podczas sprawdzania kodu (Analyze > Inspect Code…).
Kompilacje wiersza poleceń
Aby włączyć te sprawdzenia w kompilacjach wiersza poleceń, dodaj ten wiersz do pliku build.gradle:
Dynamiczny
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Pełny zestaw konfiguracji obsługiwanych w lintOptions znajdziesz w dokumentacji Androida dotyczącej DSL Gradle.
Następnie uruchom ./gradlew lint z wiersza poleceń.