Dziś znowu ketchup, ale tym razem coś, czego w Javie nie ma. Jest w scali, jest w językach funkcyjnych, ale w javie nie ma.

Co to pattern matching?

W najprostszych słowach jest to konstrukcja języka, która pozwala na budowanie czegoś w rodzaju rozbudowanych instrukcji warunkowych, ale z decydowanie prostszy i przyjemniejszy dla oka sposób. Zazwyczaj wyrażenie takie dopuszcza też pewne skrótowe zapisy, które w postaci wyrażenia warunkowego były, by trudne do zrozumienia.

W Kotlinie dopasowanie wzorców (bo tak to się po polsku nazywa) odbywa się z użyciem słowa kluczowego when. Najprostszym zastosowaniem jest użycie zamiast ifa:

Listing 1. Najprostsze użycie dopasowań

fun main(args: Array<String>) {

    val person = Person("Jan", "Kolwaski", "kowalski@jan.pl", 32L, null);

    when(person.firstName) {
        "Jan" -> print("ok")
        else -> print("nok")

    }
}

Trochę przykładów

Oczywiście nie tylko proste dopasowania jak to powyżej jest możliwe. Spójrzmy na dopasowanie za pomocą sprawdzenia typu:

Listing 2. Dopasowanie typu

open class Sex;
class Man : Sex();
class Woman : Sex();

fun main(args: Array<String>) {

    val s:Sex;

    s = Man();

    when (s) {
        is Man -> print("Sir!")
        is Woman -> print("Lady!")
        is Sex -> print("No cześć!")
    }
}

Zamiana kolejności warunków spowoduje inne zachowanie programu. Generalna zasada jest taka, że dla danej wartości jest wykonywany kod pierwszego pasującego dopasowania.

Listing 3. Zamiana kolejności wzorców

open class Sex;
class Man : Sex();
class Woman : Sex();

fun main(args: Array<String>) {

    val s:Sex;

    s = Man();

    when (s) {
        is Sex -> print("No cześć!")
        is Man -> print("Sir!")
        is Woman -> print("Lady!")
    }
}

## No cześć!

W przypadku gdy żaden wzorzec nie zostanie dopasowany, to nic się nie stanie:

Listing 4. Brak dopasowania

open class Sex;
class Man : Sex();
class Woman : Sex();

fun main(args: Array<String>) {

    val s:Sex;

    s = Man();

    when (s) {
        is Woman -> print("Lady!")
    }
}

Słowo kluczowe else z listingu 1 podpowiada nam, że jeżeli nie znajdziemy dopasowania, to zostanie wykonany kod związany z tym, specjalnym, dopasowaniem. Możemy też popracować z bardziej zawiłymi warunkami:

Listing 5. Dopasowania z zakresami i metodami

fun main(args: Array<String>) {

    val i: Int = 1

    when (i) {
        0, 1 -> print("Binary!")
        in 0..10 -> print("Decimal!")
        parseInt(args[0]) -> print("Eq param")
        else -> print("WUT?")
    }
}

W tym kodzie mamy dopasowania w zakresie za pomocą in, które możemy oczywiście negować za pomocą !in. Obecne jest też dopasowanie z metodą, ale zawiera ono małą pułapkę. Jeżeli będziemy chcieli dopasować trzeci wzorzec, to całość wysypie się z ArrayIndexOutOfBoundsException. Nie za bardzo jest jak to obejść, ponieważ musimy zachować zgodność typów pomiędzy parametrem, a wzorcem. Jedyną metodą jest delegacja tego dopasowania do osobnej metody, która przyjmie dwa parametry i zwróci Int tak, by można było wzorzec zastosować. Jednak takie rozwiązanie ma dużo wewnętrznego bleh.

Podsumowanie

Dopasowanie wzorców w kotlinie działa w sposób zbliżony do instrukcji switch w javie. Instrukcja when jest jednak zacznie bardziej elastyczny i pozwala na znacznie więcej.