Jak stworzyć klasę pomocniczą w Kotlinie
TL;DR: możesz utworzyć klasę pomocniczą, umieszczając metody wewnątrz obiektu lub definiując funkcje na poziomie pakietu. Jeśli integrujesz swój kod z kodem Java i potrzebujesz prawdziwych statycznych metod, to możesz je zawsze umieścić w obiekcie i opatrzyć adnotacją @JvmStatic. Jeśli chcesz dowiedzieć się więcej, zapraszamy do artykułu!
Kotlin jest wieloplatformowym, typowanym statycznie językiem. Został on zaprojektowany jako język w pełni kompatybilny z Javą. Rozwój Kotlina jest sponsorowany przez JetBrains i Google za pośrednictwem Fundacji Kotlin. To dobry czas, aby zastanowić się nad jego użyciem w swoim następnym projekcie, ponieważ Google ogłosiło, że jest to główny język rekomendowany do pisania aplikacji na Androida.
Kiedy po raz pierwszy korzystałem z Kotlina, miałem problem z napisaniem klasy pomocniczej. Zadałem zatem pytanie na Stack Overflow. Zdałem sobie potem sprawę, że istnieje na to wiele sposobów. Zanim jednak przejdziemy do pisania klasy pomocniczej, musimy wyjaśnić kilka podstawowych zagadnień.
Słowo kluczowe static
Metody statyczne są znane programistom Javy. Deklaruje się je za pomocą słowa kluczowego i wywołuje bezpośrednio przez nazwę klasy. Czy możemy zrobić to samo w Kotlinie? Odpowiedź brzmi „tak”, ale w inny sposób.
W przeciwieństwie do Javy, w Kotlinie nie ma niczego, co ma w nazwie „statyczny”.
Jak więc możemy to osiągnąć? Zobaczmy!
Obiekty
W Kotlinie istnieją 3 typy obiektów: Object Declaration, Companion Object oraz Object Expression.
Obiekt — przydatny typ danych w Kotlinie
Object Declaration
Wzorzec Singleton zawsze się przydaje, a Kotlin ułatwia jego deklarację:
object SaberFactory {
fun makeLightSaber(powers: Int): LightSaber {
return LightSaber(powers)
}
}
Oto Object Declaration o nazwie następującej po słowie kluczowym object
.
Podobnie jak singleton, istnieje tylko jedna instancja obiektu, który jest tworzony, gdy pierwszy raz potrzebny jest do niego dostęp, a wszystko w sposób w bezpieczny dla wątków.
Aby odnieść się do obiektu, używamy bezpośrednio jego nazwy:
val saber = SaberFactory.makeLightSaber(150)
Companion Object
Companion object łączy się z klasą przez słowo kluczowe companion
.
class Saber(val powers: Int) {
companion object Factory {
fun makeLightSaber(powers: Int): LightSaber {
return LightSaber(powers)
}
}
}
Companion object może służyć do zadeklarowania, aby metoda była powiązana z klasą, a nie z jej instancjami.
Companion object to również singleton, więc można uzyskać bezpośredni dostęp do jego metod:
val saber = Saber.makeLightSaber(150)
Różnica między singletonem, a standardową klasą
Największą różnicą jest to, że nie można zainicjalizować obiektu. W rzeczywistości obiekt jest tylko typem danych z pojedynczą instancją, więc jeśli chcemy znaleźć coś podobnego w Javie, byłby to wzorzec Singleton. Być może najlepszym sposobem na pokazanie różnicy jest spojrzenie na zdekompilowany kod Kotlin w formie Java. W Kotlinie deklarujemy obiekt i normalną klasę:
object SaberFactory {
fun makeLightSaber() { /*...*/ }
}
class SaberFactory {
fun makeLightSaber() { /*...*/ }
}
Oto ekwiwalent kodu w Javie: singleton oraz standardowa klasa.
// Generated class
public final class SaberFactory {
public static final SaberFactory INSTANCE = new SaberFactory();
private SaberFactory() { }
public final void makeLightSaber() { /*...*/ }
}
public final class SaberFactory {
public final void makeLightSaber() { /*...*/ }
}
Dlaczego w Kotlinie są obiekty?
Głównym powodem jest to, że Kotlin chciał pozbyć się zmiennych statycznych, pozostawiając nam czysty język obiektowy. Kotlin zniechęca programistów do korzystania z tych pojęć, dając nam zamiast nich obiekty, które są singletonami. Singleton nie jest tak łatwy do napisania w Javie. Jeśli dwa wątki uzyskują dostęp do singletonu jednocześnie, to może to wygenerować dwie instancje obiektu.
Wywoływanie Kotlina z Javy
Kod Kotlina można łatwo wywołać z Javy. Istnieją jednak pewne różnice, które wymagają uwagi.
Kotlin w 100% wymienia się z Javą
Funkcje na poziomie pakietu
Są to wszystkie funkcje zadeklarowane w pliku źródłowym niezawarte w klasie lub obiekcie. Wszystkie funkcje zadeklarowane w pliku app.kt
w pakiecie org.example
są skompilowane do javowych statycznych metod klasy o nazwie org.example.AppKt
(która jest nazwą klasy w PascalCase + Kt
). A dzieje się to, aby:
package org.example
fun makeLightSaber() { /*...*/ }
Co zmieni się w:
package org.example
// Generated class
class AppKt {
public static void makeLightSaber() { /*..*/ }
}
Następnie używamy AppKt
w Javie, aby wywołać metodę:
org.example.AppKt.makeLightSaber();
Uwaga: w przeszłości wszystkie funkcje najwyższego poziomu w tym samym pakiecie stawały się pojedynczą klasą Javy nazwaną po tym właśnie pakiecie. Jednak z jakiegoś powodu, JetBrains postanowił pozbyć się pojedyńczej fasady i zamiast tego użyć nazwy pliku dla wygenerowanej klasy (app.kt
→ AppKt.class
). A co jeśli chcemy zmienić nazwę AppKt na inną?
@file:JvmName
Nazwę wygenerowanej klasy Javy można zmienić za pomocą adnotacji @JvmName
@file:JvmName("SaberUtils")
package org.example
fun makeLightSaber() { /*...*/ }
Następnie używamy w Javie SaberUtils
do wywołania metody zamiast AppKt
:
org.example.SaberUtils.makeLightSaber();
Gdy mamy wiele plików, które generują taką samą nazwę klasy w Javie (ta sama nazwa pakietu, pliku lub to samo @JvmName
) to błąd. Jak więc możemy to naprawić?
@file:JvmMultifileClass
Kompilator ma możliwość wygenerowania pojedynczej klasy o określonej nazwie, która będzie zawierać deklaracje ze wszystkich plików, które zawierają tę nazwę. Aby to osiągnąć, użyj adnotacji @JvmMultifileClass
we wszystkich plikach.
// lightsaber.kt
@file:JvmName("SaberUtils")
@file:JvmMultifileClass
package org.example
fun makeLightSaber() { /*...*/ }
// darksaber.kt
@file:JvmName("SaberUtils")
@file:JvmMultifileClass
package org.example
fun makeDarkSaber() { /*...*/ }
Możemy następnie wywołać obie metody w Javie za pomocą SaberUtils
:
org.example.SaberUtils.makeLightSaber();
org.example.SaberUtils.makeDarkSaber();
@JvmStatic
Jak wspomniałem powyżej, Kotlin reprezentuje funkcje na poziomie pakietu jako metody statyczne. Może jednak również generować metody statyczne dla metod zdefiniowanych w obiektach, jeśli dodasz do nich adnotacje @JvmStatic
. Jeśli jej użyjesz, kompilator wygeneruje zarówno metodę statyczną w dołączającej klasie obiektu, jak i metodę instancyjną w samym obiekcie. Na przykład:
object SaberFactory {
@JvmStatic fun makeStaticSaber() { /*..*/ }
fun makeNonStaticSaber() { /*..*/ }
}
W Javie można używać obu zarówno jako metody statycznej, jak i normalnej (singletona).
SaberFactory.makeStaticSaber(); // works fine
SaberFactory.makeNonStaticSaber(); // error
SaberFactory.INSTANCE.makeNonStaticSaber(); // works, call through the singleton INSTANCE
SaberFactory.INSTANCE.makeStaticSaber(); // works too
Klasy pomocnicze w Kotlinie
Pierwsze podejście
Użyj obiektu. Utwórz metodę w obiekcie:
object SaberUtils {
fun makeLightSaber(powers: Int): LightSaber {
return LightSaber(powers)
}
}
Można wywołać tę metodę z Kotlina, ale przy wywoływaniu z Javy musisz dodać INSTANCE
, ponieważ jest to zwykła metoda instancji singletonu, a nie metoda statyczna.
// Call from Kotlin
SaberUtils.makeLightSaber(150)
// Call from Java
SaberUtils.INSTANCE.makeLightSaber(150)
Drugie podejście
Użyj funkcji na poziomie pakietu (bez klasy lub obiektu):
@file:JvmName("SaberUtils")
@file:JvmMultifileClass
fun makeLightSaber(powers: Int): LightSaber {
return LightSaber(powers)
}
Teraz możesz wywołać SaberUtils.makeLightSaber()
z kodu Java. W kodzie Kotlin pozwala to jednak tylko na bezpośrednie użycie metody makeLightSaber()
(bez prefiksu SabreUtils
), ponieważ nie wiąże się to z żadną klasą.
Co jest lepsze?
Drugie podejście jest w Kotlinie dosyć idiomatyczne. Nie ma właściwie potrzeby umieszczania metod pomocniczych w czymkolwiek. Używanie funkcji zdefiniowanych na poziomie pakietu to nic złego, szczególnie biorąc pod uwagę, że z takich funkcji składa się większość biblioteki standardowej Kotlina.
Jest też kilka powodów, które przemawiają za pierwszym podejściem.
- Użyj obiektu, aby uniknąć wyświetlania wszystkich sugestii autouzupełniania podczas pisania.
- Jako programista Javy, zamiast większości metod utils wolałbym
Utils.foo()
niżfoo()
zarówno w Javie, jak i w Kotlinie.
Jest jeszcze trzecie rozwiązanie: umieszczenie metod w obiekcie i użycie @JvmStatic na każdej z nich.
Oryginał tekstu w języku angielskim możesz przeczytać tutaj.