Ten dokument to zbiór reguł tworzenia publicznych interfejsów API w języku Java i Kotlin, który ma sprawić, że po użyciu kodu z innego języka może on zostać uznany za idiomatyczny.
Ostatnia aktualizacja: 18.05.2018 r.
Java (do konsumpcji Kotlin)
Brak twardych słów kluczowych
Nie używaj żadnych twardych słów kluczowych Kotlin jako nazwy metod czy pól. Wymaga to użycia grawisu, aby uciec, gdy wywołujesz Kotlin. Dozwolone są miękkie słowa kluczowe, słowa kluczowe modyfikujące i specjalne identyfikatory.
Na przykład funkcja when
Mockito wymaga grawisów, gdy jest używany z Kotlin:
val callable = Mockito.mock(Callable::class.java) Mockito.`when`(callable.call()).thenReturn(/* … */)
Unikaj nazw rozszerzeń Any
Jeśli nie jest to absolutnie konieczne, unikaj używania w przypadku metod nazw funkcji rozszerzeń w Any
lub nazw właściwości rozszerzeń w Any
w polach. Chociaż metody i pola składowe zawsze mają pierwszeństwo przed funkcjami lub właściwościami rozszerzenia Any
, określenie, które z nich jest wywoływane, może być trudne podczas odczytywania kodu.
Adnotacje z wartością null
Każdy parametr, zwrot i typ pola w publicznym interfejsie API powinien mieć adnotację z możliwością wartości null. Typy bez adnotacji są interpretowane jako typy „platform”, które mają niejednoznaczną wartość null.
Domyślnie flagi kompilatora Kotlin uznają adnotacje JSR 305, ale oznaczają je ostrzeżeniami. Możesz też ustawić flagę, dzięki której kompilator będzie traktować adnotacje jako błędy.
Ostatnie parametry Lambda
Typy parametrów, które kwalifikują się do konwersji SAM, powinny być ostatnie.
Na przykład podpis metody RxJava 2’s Flowable.create()
jest zdefiniowany jako:
public staticFlowable create( FlowableOnSubscribe source, BackpressureStrategy mode) { /* … */ }
Ponieważ FlowableOnSubskrybuj kwalifikuje się do konwersji SAM, wywołania funkcji tej metody z Kotlin wyglądają tak:
Flowable.create({ /* … */ }, BackpressureStrategy.LATEST)
Gdyby jednak parametry zostały odwrócone w podpisie metody, wywołania funkcji mogą używać składni lambda na końcu:
Flowable.create(BackpressureStrategy.LATEST) { /* … */ }
Prefiksy usługi
Aby metoda została przedstawiona jako właściwość w Kotlin, należy użyć ścisłego prefiksu w stylu „bean”.
Metody dostępu wymagają prefiksu „get”, a w przypadku metod zwracających wartości logiczne 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 mutatora 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 ujawnić metody jako właściwości, nie używaj niestandardowych prefiksów, takich jak „hasła”, „set” czy „get” (nie pobieraj). Metody z niestandardowymi prefiksami nadal można wywoływać jako funkcje, które mogą być dopuszczalne w zależności od ich działania.
Przeciążenie operatora
Zwróć uwagę na nazwy metod, które umożliwiają użycie specjalnej składni witryny wywołań (np. przeciążania operatorów) w Kotlin. Upewnij się, że takie nazwy metod mają sens w przypadku ich 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żytku w języku Java)
Nazwa pliku
Jeśli plik zawiera funkcje lub właściwości najwyższego poziomu, zawsze dodawaj do niego adnotacje @file:JvmName("Foo")
, aby zapewnić ładną nazwę.
Domyślnie członkowie najwyższego poziomu w pliku MyClass.kt znajdą się w klasie o nazwie MyClassKt
, która jest nieatrakcyjna i przecieka język jako szczegóły implementacji.
Rozważ dodanie @file:JvmMultifileClass
, aby połączyć w 1 klasie użytkowników najwyższego poziomu z wielu plików.
Argumenty lambda
Interfejsów pojedynczej metody (SAM) zdefiniowanych w języku Java można implementować zarówno w kotlinach, jak i w Javie, korzystając ze składni lambda, która implementuje się w sposób idiomatyczny. Kotlin ma kilka opcji definiowania takich interfejsów, z których każda różni się nieco od siebie.
Preferowana definicja
Funkcje wyższego rzędu, które mają być używane w Javie, nie powinny przyjmować typów funkcji, które zwracają Unit
, ponieważ wymagają one zwracania wartości Unit.INSTANCE
przez elementy wywołujące Java.
Zamiast umieszczać typ funkcji w podpisie, używaj interfejsów funkcjonalnych (SAM).
Do definiowania interfejsów, które mają być używane jako lambda, rozważ też stosowanie interfejsów funkcjonalnych (SAM) zamiast zwykłych. Pozwoli to na idiomatyczne użycie usługi Kotlin.
Przeanalizujmy tę definicję Kotlina:
fun interface GreeterCallback { fun greetName(String name) } fun sayHi(greeter: GreeterCallback) = /* … */
Po wywołaniu z Kotlin:
sayHi { println("Hello, $it!") }
Po wywołaniu z Javy:
sayHi(name -> System.out.println("Hello, " + name + "!"));
Nawet jeśli typ funkcji nie zwraca wartości Unit
, warto stworzyć interfejs w formie nazwanej, aby umożliwić elementom wywołującym go implementowanie za pomocą nazwanej klasy, a nie tylko lambda (w kotlinach i w języku Java).
class MyGreeterCallback : GreeterCallback { override fun greetName(name: String) { println("Hello, $name!"); } }
Unikaj typów funkcji, które zwracają wartość Unit
Przeanalizujmy tę definicję Kotlina:
fun sayHi(greeter: (String) -> Unit) = /* … */
Wymaga to od wywołań Javy funkcji Unit.INSTANCE
:
sayHi(name -> { System.out.println("Hello, " + name + "!"); return Unit.INSTANCE; });
Unikaj używania funkcjonalnych interfejsów, gdy implementacja ma mieć stan
Jeśli implementacja interfejsu ma mieć stan, używanie składni lambda nie ma sensu.
Przykładem jest porównywalny, ponieważ służy do porównywania wartości this
z other
, a lambda nie ma parametru this
. Brak prefiksu fun
wymusza użycie składni object : ...
, która umożliwia używanie stanu przez element wywołujący.
Przeanalizujmy tę definicję Kotlina:
// No "fun" prefix. interface Counter { fun increment() }
Zapobiega składniom lambda w Kotlin, wymagając tej dłuższej wersji:
runCounter(object : Counter { private var increments = 0 // State override fun increment() { increments++ } })
Unikaj nazw ogólnych (Nothing
)
Typ, którego parametr ogólny to Nothing
, jest dostępny w języku Java jako typy nieprzetworzone. Nieprzetworzone typy są rzadko używane w Javie i należy ich unikać.
Wyjątki dla dokumentów
Funkcje, które mogą zgłaszać zaznaczone wyjątki, powinny udokumentować je za pomocą funkcji @Throws
. Wyjątki środowiska wykonawczego powinny być udokumentowane w KDoc.
Pamiętaj o interfejsach API, do których deleguje funkcja, ponieważ mogą one zgłaszać zaznaczone wyjątki, które Kotlin zezwala na ich rozpowszechnianie dyskretnie.
Kopie obronne
Gdy zwracasz z publicznych interfejsów API udostępnione lub nienależące do Ciebie kolekcje, umieść je w kontenerze, którego nie można zmienić, lub wykonaj kopię obronną. Chociaż Kotlin egzekwuje swoją właściwość tylko do odczytu, nie jest to egzekwowane po stronie Javy. Bez kodu obronnego i kodu defensywnego niezmienność może zostać naruszona przez zwrócenie długoterminowego odniesienia do kolekcji.
Funkcje towarzyszące
Aby funkcje publiczne w obiekcie towarzyszącym były widoczne jako metoda statyczna, muszą być oznaczone adnotacją @JvmStatic
.
Bez adnotacji te funkcje są dostępne tylko jako metody instancji w statycznym polu Companion
.
Nieprawidłowo: brak adnotacji
class KotlinClass { companion object { fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.Companion.doWork(); } }
Prawidłowa adnotacja: @JvmStatic
adnotacja
class KotlinClass { companion object { @JvmStatic fun doWork() { /* … */ } } }
public final class JavaClass { public static void main(String... args) { KotlinClass.doWork(); } }
Stałe towarzyszące
Publiczne właściwości innych niż const
, które są efektywnymi stałymi w elemencie companion object
, muszą mieć adnotację @JvmField
, aby były widoczne jako pole statyczne.
Bez adnotacji te właściwości są dostępne tylko jako instancja o dziwnej nazwie „getters” w statycznym polu Companion
. Użycie @JvmStatic
zamiast @JvmField
przenosi dziwne nazwy „getters” do metod statycznych klasy, co nadal jest nieprawidłowe.
Nieprawidłowo: 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()); } }
Niepoprawna: adnotacja @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()); } }
Prawidłowa adnotacja: @JvmField
adnotacja
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 nazwy
Kotlin ma inne konwencje wywoływania niż Java, które mogą zmieniać sposób nadawania nazw funkcjom. Użyj @JvmName
, aby projektować nazwy w taki sposób, aby były idiomatyczne w obu konwencjach językowych lub pasowało do odpowiednich standardowych nazw bibliotek.
Dzieje się tak najczęściej w przypadku funkcji rozszerzeń i właściwości rozszerzeń, ponieważ lokalizacja typu odbiorcy jest inna.
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); }
Przeciążenie funkcji w przypadku wartości domyślnych
Funkcje, których parametry mają wartość domyślną, muszą używać parametru @JvmOverloads
.
Bez tej adnotacji nie można wywołać funkcji z użyciem jakichkolwiek wartości domyślnych.
Jeśli używasz metody @JvmOverloads
, sprawdź wygenerowane metody, aby upewnić się, że każda ma sens. Jeśli nie, wykonaj jedną lub obie z tych refaktoryzacji do momentu, aż spełnią Twoje oczekiwania:
- Zmień kolejność parametrów, aby preferować te, których wartości domyślne znajdują się bliżej końca.
- Przenieś wartości domyślne do ręcznych przeciążeń funkcji.
Nieprawidłowo: 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"); } }
Prawidłowa adnotacja: @JvmOverloads
adnotacja.
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"); } }
Likwidacja szczotek
Wymagania
- Wersja Android Studio: 3.2 Canary 10 lub nowsza
- Wersja wtyczki Androida do obsługi Gradle: 3.2 lub nowsza
Obsługiwane testy
Dostępne są teraz testy Android Lint, które pomogą Ci wykrywać i zgłaszać niektóre z opisanych wyżej problemów ze zgodnością. Obecnie wykrywane są tylko problemy w Javie (do wykorzystania Kotlin). Obsługiwane są te kwestie:
- Nieznana wartość null
- Dostęp do usługi
- Brak twardych słów kluczowych Kotlin
- Ostatnie parametry Lambda
Android Studio
Aby włączyć te weryfikacje, kliknij Plik > Ustawienia > Edytor > Inspekcje i w sekcji interoperacyjności Kotlin sprawdź reguły, które chcesz włączyć:
Gdy zaznaczysz reguły, które chcesz włączyć, nowe testy będą przeprowadzane po uruchomieniu inspekcji kodu (Analiza > Sprawdź kod...).
Kompilacje z poziomu wiersza poleceń
Aby włączyć te kontrole w kompilacjach wiersza poleceń, dodaj ten wiersz do pliku build.gradle
:
Odlotowy
android { ... lintOptions { enable 'Interoperability' } }
Kotlin
android { ... lintOptions { enable("Interoperability") } }
Pełną listę konfiguracji obsługiwanych w lintOptions znajdziesz w dokumentacji DSL w Androidzie Gradle.
Następnie uruchom polecenie ./gradlew lint
z poziomu wiersza poleceń.