Pracując już trochę lat komercyjnie jako programista miałem styczność z językami takimi jak Java, Groovy, TypeScript, JavaScript i Cobol. Nie miałem przyjemności pracować z Kotlinem, o którym słyszałem dużo dobrych rzeczy. Teraz, gdy szykuje się na zmianę zawodową, w nowej pracy będę miał okazję popisać aplikacje właśnie w tym języku. Z tego powodu postanowiłem spojrzeć do dokumentacji i popatrzeć czym Kotlin może mnie zaskoczyć. Z tej okazji tworzę też ten artykuł, aby dać też Tobie pogląd na ten język. Oczywiście o ile nie miałeś/miałaś już z nim styczności.
Definiowanie metod
1
2
3
4
fun max(a: Int, b: Int): Int {
if (a > b) return a;
return b;
}
Definiując metodę musimy skorzystać ze słowa kluczowego fun
, co odróżnia to podejście od Javy. Wskazanie na typ zwracany definiujemy na końcu metody po znaku :
, podobnie jak typy parametrów metody. Jednak co najbardziej przykuło moją uwagę to składnia zaprezentowana poniżej.
1
fun sum(a: Int, b: Int) = a + b
Ciało metody może być prostym wyrażeniem, jednolinijkowcem. W takim przypadku typ zwracany jest dedukowany z wyrażenia. Natomiast jeśli metoda miałaby klasyczny kształt to domyślnym typem zwracanym, gdy żaden nie jest podany, jest void, czyli Unit
.
1
2
3
fun printSum(a: Int, b: Int) {
println("sum of $a and $b is ${a + b}")
}
Zmienne - val i var
Kotlin potrafi się domyślić jaki będzie typ zmiennej, który deklarujemy. Nie ma potrzeby jej podawania explicite. Javowcy poznali tą możliwość dopiero w wersji 10. Do definiowania zmiennych w Kotlin służą nam słowa kluczowe var
oraz val
. Różnica pomiędzy nimi jest taka, że do zmiennej typu var
możemy przypisać inną wartość w dalszej części programu. Natomiast raz przypisana wartość do zmiennej typu val
zostaje z nią do końca trwania danego bloku kodu. Wydaje mi się, że to działa tak jak słowo kluczowe final
w Javie.
1
2
3
4
5
val x = 5
var y = 3
// x += 1 // compilation error
y += 1 // y = 4
Niemutowalne klasy
1
class Point(val x: Int, val y: Int) {}
Tyle wystarczy, aby zdefiniować niemutowalną klasę znaną w Javie jako record
od wersji 14. Mamy tutaj strukturę danych reprezentującą punkt. Stworzona instancja klasy Point
nie może mieć zmienionych wartości pól x
oraz y
, ponieważ są one zdefiniowane jako val
. Istnieje tylko możliwość odczytania ich wartości. Dodatkowo klasy w Kotlin są domyślnie niemożliwe do rozszerzenia. Żeby zezwolić na taki zabieg musimy to zrobić samodzielnie stosując słowo kluczowe open
. Dla mnie tak powinno to działać w każdym języku programowania. Domyślnie większość konstrukcji powinna być na poziomie najbardziej restrykcyjnym, aby to programista świadomie decydował co powinnien widzieć cały świat, a czego nie.
1
2
3
open class Length
class Point(val x: Int, val y: Int): Length {}
String templates
Myślę, że tej funkcjonalności najbardziej brakuje Javowcą, co nie String.format
?
1
2
3
4
val amount = 5
val thing = "apple"
val sentence = "I have ${amount} ${thing}${if (amount > 1) "s" else ""}"
Czy taka składnia nie jest po prostu piękna?
For loop
W Python pamiętam, że w zgrabny sposób można iterować po elementach w kolekcji. To samo zastałem w Kotlinie. Wykorzystanie słowa kluczowego in
w pętli for
wygląda naprawdę czytelnie. Dodatkowo w prosty sposób możemy również wyciągnąć indeksy kolejnych elementów obecnych w kolekcji, bez korzystania z konstrukcji fori
. Java w tym temacie zostaje w tle, no może poza foreach
.
1
2
3
4
val numbers = listOf("one", "two", "three")
for (number in numbers) {
println(number)
}
1
2
3
4
val numbers = listOf("one", "two", "three")
for (index in numbers.indices) {
println("number at $index is ${numbers[index]}")
}
When statement
Zamiast switch
w Kotlin mamy dostępne słowo kluczowe when
. I jego składania wygląda naprawdę imponująco. Widać trend w Javie, że właśnie do takiego efektu dążą też twórcy tego języka.
1
2
3
4
5
6
7
8
fun resolveDescription(obj: Any): String =
when (obj) {
1 -> "One"
"Hello" -> "Greeting"
is Long -> "Long"
!is String -> "Not a string"
else -> "Unknown"
}
W when
możemy mieszać różne koncepcje. Od prostego przypadku pojedynczej wartości aż po pattern matching, nawet z zaprzeczeniem. Z mojego punktu widzenia naprawdę warto zwrócić uwagę na tą konstrukcję.
Ranges
Tutaj znowu wygląda mi to na koncept podobny do tego z Pythona.
1
2
3
for (x in 1..10) {
print(x)
}
Co ciekawe możemy ze słowa kluczowego in
korzystać też w if
w kontekście kolekcji. Daje to nam to bardzo przyjemną dla oka składnię.
1
2
3
4
5
6
val x = 6
val y = 10
if (x in 0..y step 2) {
println("fits in range")
}
Jak widać możemy też nadawać skok przy użyciu step
jak i dekrementować - downTo
.
1
2
3
for (x in 9 downTo 0 step 3) {
print(x) // 9630
}
Lambda expressions
1
2
3
4
5
6
val tools = listOf("c@r", "l@ptop", "mouse", "keybo@rd")
tools
.filter { it.contains("@") }
.map { it.replace("@", "a") }
.sortedBy { it }
.forEach { println(it) }
Wydaje mi się, że koncept it
jest zaciągnięty z Groovy. Nie wiem co było pierwsze, ale właśnie stamtąd znam to podejście. W fajny sposób pozwala nam to nie pisać zbędnych zmiennych w lambdzie, które najczęściej powidują konflikt w nazewnictwie. Dodatkowo wykorzystanie nawiasów klamrowych sprawia, że czuję się jakbym pisał testy w Spocku.
Brak null
To chyba największy szok jaki przeżyłem dawno temu, gdy się o tym powiedziałem. Znowu, safety first. Żeby przypisać null do zmiennej muszę to podać z premedytacją wykorzystując znak zapytania. To samo tyczy się wartości zwracanej z metody.
1
2
3
val x = null;
// val y: Int = null;
val z: Int? = null;
1
2
3
fun parseInt(str: String): Int? {
// implementation
}
To mega pomoc w walce z błędem, który kosztował miliard dolarów. Dla mnie to kolejny plus w porównaniu do Javy.
is
zamiast instanceof
Za tą zmianą nie tylko idzie semantyka. Również dostajemy w komplecie to za czym Java tak goni.
1
2
3
4
5
6
fun countLetters(obj: Any): Int? {
if (obj is String) {
return obj.length // automatic cast
}
return null
}
Kotlin za nas z automatu robi rzutowanie. Co ciekawe, nie musimy w ogóle robić przypisania do nowej zmiennej jak w Javie. Dalej korzystamy z tej samej zmiennej, ale zawężając jej typ.
Podsumowanie
Po tym krótkim zagłębieniu się w meandry Kotlina muszę przyznać, że podoba mi się ten język. Jego twórcy naprawdę sporo nauczyli się z błędów jakie popełniła starsza koleżanka Kotlina, Java. Także mam nadzieję, że ten krótki wpis również i Ciebie zachęcił do spróbowania swoich sił w Kotlinie. Być może jesteś kolegą lub koleżanką z większym doświadczeniem i mógłbyś/mogłabyś podzielić się w komentarzu minusami jakie dostrzegasz w Kotlinie?