Cześć. Tym razem wpis typowo programistyczny, przeznaczony głównie do programistów Javy (właściwie jeden z trzech wpisów, to będzie minicykl o transformacjach). W tym wpisie nie pokażę Ci jak zainstalować Kotlina. Pokażę Ci natomiast kilka podstawowych fragmentów kodów Javowych oraz ich transformację do kodu Kotlinowego. Zobaczysz, jak bardzo niektóre rzeczy można uprościć uzyskując taki sam lub zbliżony efekt końcowy. Ten cykl wpisów to dobry punkt wyjścia dla osób, które chcą wdrożyć Kotlina do projektu i zaczynają przepisywać niektóre funkcjonalności z Javy. Warto zaznaczyć, iż w tym poście snippety kodu Kotlinowego są edytowalne i możesz…je uruchamiać Gorąco zachęcam, łatwo sprawdzić rezultat i zweryfikować informacje ode mnie. Dzisiaj część pierwsza, część następna niebawem.
Definiowanie zmiennych
Na początek napiszemy jeden prosty program w dwóch językach. Stworzymy dwie zmienne, które są integerami. Jedna nie może zmienić wartości, druga może, choćby na nulla (to jest bardzo ważne w kontekście Kotlina!). Zacznijmy od Javy.
Java:
1 2 3 4 5 6 7 8 9 10 11 12 | public
static
void main(String[] args)
{ final Integer finalInt = 66; System.out.println("finalInt. Int type? " + Integer.class.isInstance(finalInt) + ". Value: " + finalInt); // finalInt = 67; nie zadziała Integer notFinalInt = 70; notFinalInt = 71; System.out.println("notFinalInt. Int type? " + Integer.class.isInstance(notFinalInt) + ". Value: " + notFinalInt); // notFinalInt = "71"; nie zadziała notFinalInt = null; } |
Jak widzimy, aby zmienna w Javie nie mogła się zmienić musimy do deklaracji dodać słówko najważniejsze final. Dla pewności jeszcze chcemy sprawdzić typ zmiennej finalInt. Definitywnie jest tam Integer. Druga zmienna (notFinalInt) może zmienić wartość. przez cały czas jest to typ Integer. Oczywiście, Java jest językiem silnie typowanym, czyli już zadeklarowanego typu zmiennej nie zmienimy, a więc do zmiennej typu Integer nie przypiszemy “71” jako String. Z przypisaniem wartości null problemu w Javie nie ma. Możemy go przypisać gdziekolwiek (pod warunkiem, iż zmienna nie jest finalna).
Teraz przejdźmy do Kotlina.
Kotlin:
fun main(args: Array<String>){ val finalInt = 66 println("finalInt. Int type? ${finalInt is Int}. Value: $finalInt") // finalInt = 67 nie zadziała var notFinalInt = 70 notFinalInt = 71 println("notFinalInt. Int type? ${notFinalInt is Int}. Value: $notFinalInt") // notFinalInt = "71" // nie zadziała // notFinalInt = null // nie zadziała }Tak. Deklaracja val finalInt = 66 w Kotlinie jest równa final Integer finalInt = 66; w Javie. Dlaczego? Przede wszystkim, w Kotlinie nie ma słówka final. Przy deklaracje zmiennej używamy albo słówka var (variable) albo val (value). W pierwszym przypadku zmienna nie jest finalna, w drugim jest. Duży skrót. Ponadto, nie widać nigdzie deklaracji typu. Dlaczego skoro Kotlin też jest silnie typowany? Ponieważ występuje tu coś takiego jak “type interference”. o ile Kotlin jest w stanie wywnioskować jaki jest typ zmiennej to od razu typ jest zadeklarowany bez pisania go wprost!
W następnej linijce sprawdzamy wartość i czy faktycznie jest to Int. W Kotlinie typ można sprawdzić poprzez użycie słówka is. Czytelnie, prawda? Może Cię zaskoczyć też ten zapis wiadomości, jakieś dolary itd. Jest to tzw “string interpolation”. Nie musimy wszędzie robić plusów, możemy nazwę zmiennej poprzedzić dolarem i w tym miejscu zostanie wstawiona jego wartość do łańcucha. o ile mamy natomiast wywołanie funkcji, odwołanie do pola lub coś bardziej złożonego to już musimy to otoczyć “wąsami”. Następnie, wykomentowane przypisanie wartości 67 nie zadziała, gdyż zadeklarowaliśmy finalInt jako zmienną finalną.
Dalej tworzmy kolejną zmienną, notFinalInt, tym razem poprzedzoną słówkiem var. Typ równiez zostanie poprawnie wywnioskowany (wypisanie na dole to potwierdzi), a nadpisanie wartości nie stanowi problemu. Przypisanie wartości “71” nie zadziała, co potwierdza, iż typ jest wcześniej określony (próbujemy przypisać Stringa do Inta, nie może być!). Natomiast, przypisanie wartości null odziwo też nie zadziała, co może być dość dziwne. Dlaczego? O tym zaraz.
Ochrona przed nullem
Chyba nie ma programisty w świecie Javy, który by nie doświadczył czegoś takiego jak NullPointerException. Bardzo irytujący wyjątek, często ciężki do znalezienia, szczególnie gdy mamy ciąg wywołań funkcji w jednej linijce. Prosty przykład z Javy jak się przed tym zwykle zabezpieczamy.
Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public
static
void main(String[] args)
{ List<String> stringsList = new ArrayList<String>(); stringsList.add("Ala"); if (stringsList != null) { System.out.println("List size: " + stringsList.size()); } else { System.out.println("List size: null"); } stringsList = null; if (stringsList != null) { System.out.println("List size: " + stringsList.size()); } else { System.out.println("List size: null"); } } |
W kodzie powyżej chcemy sprawdzić rozmiar listy i wypisać komunikat. o ile lista byłaby nullem to zobaczylibyśmy NullPointerException, program by się “wykrzaczył”. Musimy się zabezpieczać i sprawdzać za każdym razem czy lista jest różna od nulla, jak powyżej.
W Kotlinie domyślnie każdy obiekt jest typu nienullowego (chyba, iż przypiszemy do niego od razu nulla). o ile zmienna może być nullem to do definicji typu dodajemy pytajnik, a więc typ Int jest inny od typu Int?. Ten drugi może posiadać nulla, natomiast wiążą się z tym pewne zasady, które musimy przestrzegać. W przypadku tego pierwszego natomiast nie musimy się nullem przejmowac. Sprawdźmy to w praktyce.
fun main(args: Array<String>){ var stringList = listOf("Ala") println("List size: ${stringList.size}") // stringList = null // nie zadziala }Nie musimy się przejmować tutaj nullem. Mamy w tym przypadku pewność, iż NPE nie zostanie rzucony bo od razu wywnioskowany typ przez Kotlina to będzie List. A jak by to wyglądało gdyby typ mógł byc nullem?
fun main(args: Array<String>){ var stringList: List<String>? = listOf("Ala") println("List size: ${stringList?.size}") stringList = null println("List size: ${stringList?.size}") }Lista została zdefiniowana jako nullable, gdyż potem będe chciał do niej przypisać nulla (pytajnik po nazwie typu). Początkowo jednak lista nie posiada nulla. Mimo to sprawdzenie rozmiaru jest inne niż w poprzednim przypadku. Odwołanie do pola size w tym przypadku jest poprzedzone pytajnikiem. W Kotlinie dla typów nullowych jest to wymagane, a więc trzeba trzymać dyscyplinę przy wywołaniach i odwoływaniu się do zmiennej za każdym razem! Kod zadziała praktycznie jak powyżej, rozmiar zostanie wypisany, a różnica jest tylko w jednym znaku. Zagwozdka zaczyna się potem, jest sprawdzany rozmiar nulla. Kod natomiast zachowa się jak przykład w Javie, zostanie wypisany tekst “null”, natomiast żaden NPE nie zostanie rzucony mimo braku jakiegokolwiek ifa. Jesteśmy bezpieczni!
Oba kody zachowują się praktycznie tak samo, natomiast po raz kolejny zapis w Kotlinie jest bardziej przejrzysty. Ponadto, widać jak ważne jest projektowanie kodu na wczesnym etapie. Wyrzucając nulle z aplikacji nasz kod jest wiele schludniejszy, a zmartwień co raz mniej. o ile jednak w kotlinowym kodzie mamy świadomość, iż może nam gdzieś wartość null wskoczyć to musimy się o to troszczyć w przypadku danego obiektu za każdym razem! Chyba nie muszę mówić które podejście jest lepsze i dlaczego to bez nulli?
Definiowanie funkcji
Teraz zobaczymy jak zdefiniować dwie proste funkcje w Javie oraz jej odpowiedniki w Kotlinie. Jedna funkcja będzie zwracać wartość 42, natomiast druga będzie robić to samo oraz wypisze odpowiedni komunikat.
Java:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | class ClassWithFunctions { public static int fourtyTwo() { return 42; } public static int fourtyTwoWithMsg() { System.out.println("I am fourty two!"); return 42; } } public static void main(String[] args) { System.out.println(ClassWithFunctions.fourtyTwo()); System.out.println(ClassWithFunctions.fourtyTwoWithMsg()); } |
W Javie do stworzenia funkcji potrzebujemy klasy. Funkcje zostały zadeklarowane jako statyczne, dzięki czemu nie musimy tworzyć obiektu typu `ClassWithFunctions`. Dwie proste funkcje, publiczne, statyczne, ze zdefiniowanym typem jaki zwracają. Odpowiednik w Kotlinie:
Kotlin:
fun fourtyTwo() = 42 fun fourtyTwoWithMsg(): Int { println("I am fourty two!") return 42 } fun main(args: Array<String>) { println(fourtyTwo()) println(fourtyTwoWithMsg()) }Już na pierwszy rzut oka widać ogromne różnice! Jak już wcześniej pewnie zauważyłeś, funkcje w Kotlinie są poprzedzane wymaganym słówkiem kluczowym fun. Co ważne, funkcje domyślnie są finalne oraz publiczne! o ile chcemy, by funkcja finalna nie była, wystarczy dodać słówko open. Kolejna duża rzecz, funkcje nie muszą być deklarowane w klasie! Możemy je robić jako funkcje top-level w pliku, możemy ich również potem używać w innych miejscach. Przykładem jest używana funkcja listOf przeze mnie wcześniej, też to jest funkcja top-level. Funkcja może być także deklarowana w innej funkcji, jest wtedy dostępna tylko w niej. Bardzo przydatne, gdy mamy jedną funkcję publiczną oraz wiele prywatnych używanych tylko w tej jednej publicznej.
Funkcje w Kotlinie mają również wiele innych mocy. Na przykład funkcja fourtyTwo jest funkcją jednolinijkową która tylko zwraca wartość 42. Możemy więc zrobić coś na wzór przypisania wartości do zmiennej. Jest to tzw “expression body”. Wartość 42 zostanie zwrócona tak samo. Różnica jest taka, iż nie ma wąsów, słówka return nie używamy, a także deklaracja typu nie jest wymagana, Kotlin się domyśli. Ale to schludnie wygląda!
W drugiej funkcji już typ deklarujemy na końcu definicji, jak przy zmiennej. Dlaczego? Domyślnie funkcje w Kotlinie zwracają Unit, jest to odpowiednik void w Javie. o ile nie zdefiniujemy typu zwracanego przez “normalną” funkcję to kompilator zobaczy tam Unit. Przyjęta praktyka w Kotlinie jest, iż gdy chcemy zwrócić Unit to nie definiujemy tego, Kotlin wie. Tutaj jednak chcemy Int, więc jest to odpowiednio określone. Reszta funkcji nie rózni się od tej w Javie, wypisanie oraz return.
W funkcji main wołam obie funkcje, bez problemu, wynik jest jak powyżej. Nie potrzebuję odwoływać się do pliku albo do klasy. Wiele mniej roboty dla takiego prostego programiku.
Podsumowanie
To tyle na dziś. Kilka prostych przykładów, które już pokazują moc Kotlina. Często mniej kodu jest potrzebne do stworzenia niektórych rzeczy, często idzie za tym przejrzystość. Jeszcze raz gorąco zachęcam do sprawdzenia na własnej skórze przykładów Kotlinowych oraz pomodyfikowanie ich trochę (można to łatwo tu na stronie zrobić w tych przykładach, wystarczy kliknąć). Do przeczytania!
Pozostałe części:
Część druga
Część trzecia