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?