Następne informacje sprzyjające nauce Kotlina! Dzisiaj trochę się cofniemy co do poznawania materiału i przybliżymy sobie czym się charakteryzuje słowo kluczowe "in" w języku Kotlin. Jest to o tyle ważne, że można je podstawiać do kilku sytuacji, a każda z nich nadaje temu inne znaczenie. Poznajcie jakie i zacznijcie bardziej świadome programowanie.
Tweet |
"IN" W JĘZYKU KOTLIN. JEDNO SŁOWO, WIELE ZNACZEŃ
Nie bawmy się w budowanie jakiegoś napięcia i przejdźmy do podpunktów gdzie konkretnie możecie ujrzeć słowo kluczowe "in".
ZBIORY LICZB
Odwołując się do artykułu o pętlach "for", na 99% przypadków będziemy widzieć użycie słowa kluczowego "in" podczas iterowania po zbiorze liczb. W Kotlinie nie ma staroświeckiego podejścia, że licznik rozpoczyna się w punkcie A i ma dojść do punktu B. Wszystko opiera się na zbiorach, tak jak w języku Python, zatem oto jest podstawowe zastosowanie:
for (i in 1..10) {
}
Tablice i kolekcje tak samo:
for (n in numbers) {
}
SPRAWDZENIE CZY ELEMENT ZAWIERA SIĘ W TABLICY / KOLEKCJI
Zachęcając do obejrzenia artykułu o instrukcjach warunkowych, jeszcze raz o tym napomknę. Kotlin "uzbrojony" jest w wygodne sprawdzenie czy element znajduje się w tablicy lub kolekcji bez potrzeby pisania pętli "for" albo wywoływania metody "contains" (w zależności od tego, czy jest to tablica, czy kolekcja), tak jak musieliśmy się męczyć w języku Java:
val numbers = arrayOf(99, 313, 44)
println(1 in numbers)
Warto wspomnieć o tym, że w przypadku weryfikowania istnienia podanego elementu w zbiorze danych, mamy prawo zanegować operator stawiając znak negacji przed słowem "in" (wykrzyknik). Zatem, możemy zbadać czy dany element NIE znajduje się w tablicy lub kolekcji.
println(99 !in numbers)
INSTRUKCJA WIELOKROTNEGO WYBORU "WHEN"
Niedawno przedstawiona przeze mnie instrukcja "when" także może w sobie posiadać "in". Rzucając okiem na poniższy przypadek:
when(val number = 13) {
in numbersA -> println("Liczbę $number znaleziono w zbiorze A!")
in numbersB -> println("Liczbę $number znaleziono w zbiorze B!")
in numbersC -> println("Liczbę $number znaleziono w zbiorze C!")
}
widać, że "in" w języku Kotlin zostało użyte w środku instrukcji "when" i zapewniam, że kompilator nic nie będzie miał przeciwko temu. Powyżej zaprogramowano sprawdzenie kilku przypadków czy podana zmienna zawiera się w którymkolwiek ze zbiorów liczbowych. Jeśli liczbę znaleziono w konkretnym zbiorze, zostanie wykonana określona instrukcja (lub wiele instrukcji).
KONTRAWARIANCJA TYPU SPARAMETRYZOWANEGO
Wymagana jest wiedza z zakresu typów generycznych, aby zrozumieć sens zastosowania "in" tutaj. Domyślnie, każdy typ generyczny jest "inwariantny". Oznacza to, że w miejscu parametru typu, można "wstawić" wyłącznie referencje tego samego typu. Kiedy natomiast jest on "kontrawariantny" (a taką modyfikację nakładamy dzięki słowu "in" w języku Kotlin), mamy możliwość "wstawienia" typu klasy pochodnej w miejsce typu klasy bazowej. Przeczytajcie bardzo uważnie poprzednie zdanie!
W praktyce, to wygląda w taki sposób. Dysponując paroma klasami, które formułują hierarchię dziedziczenia:
open class Point(var x: Double, var y: Double)
open class Entity(x: Double, y: Double, val model: String) : Point(x, y)
class Character(x: Double, y: Double, model: String, hitpoints: Int) : Entity(x, y, model)
mamy jeszcze taką jedną, która dajmy na to, kalkuluje odległości pomiędzy dwoma obiektami pochodzącymi od klasy punktu:
import kotlin.math.sqrt
class DistanceCalculator<in T : Point, in U : Point> {
fun distance(t: T, u: U): Double {
val x = u.x - t.x
val y = u.y - t.y
val sum = x*x + y*y
return sqrt(sum)
}
}
Widzicie słówko "in"? Jest wstawione dwukrotnie zaraz po "otwarciu" nawiasu kątowego. To jest sygnał dla kompilatora, żeby dany parametr typu (w tym przypadku oba naraz) dopuszczał wstawienie klasy pochodnej w miejsce klasy bazowej. Utwórzmy stosowne instancje:
val entity = Entity(4.2, 5.6, "barrel")
val character = Character(80.3, 1615.4, "male", 60)
val dc: DistanceCalculator<Entity, Entity> = DistanceCalculator()
println(dc.distance(character, entity))
Trzecia linijka ma celowo wypisane jawne typy po lewej stronie instrukcji przypisania, aby pokazać Wam "clue" tego wszystkiego. Ponieważ zastosowaliśmy kontrawariancję, możemy bezkarnie zdefiniować w nawiasach kątowych KAŻDĄ klasę pochodną od "Entity", w efekcie czego mamy prawo wstawić do metody obiekt typu "Entity" albo typu "Character", gdyż obie dziedziczą od "Point" i obie są klasy "Entity" albo wyższe. Typy podane po LEWEJ stronie instrukcji decydują o tej "granicy", od której klasy wzwyż można wstawiać parametry. Gdybyśmy wstawili "Character", to wówczas instancja akceptowałaby wyłącznie obiekty klasy "Character", bądź dziedziczące od niej, zgodnie z regułą polimorfizmu.
Kontrawariancję przy użyciu "in" w języku Kotlin można zastosować wtedy i tylko wtedy, gdy:
- klasa ma funkcję, która używa parametru typu T jako typu swojego parametru
- klasa NIE zawiera żadnych funkcji zwracających parametr typu T
- klasa NIE zawiera żadnych właściwości typu "parametr typu T", bez względu na to, czy użyto "var", czy "val"
Krótko pisząc, chodzi o to żeby w żadnym miejscu, parametr typu nie służył za wartość "wyjściową" (np. zwrot wartości funkcji), tylko "wejściową" (np. parametr formalny funkcji).
![]() | ![]() |
Słowo kluczowe "in" posiada kilka różnych zastosowań, a każde z nich nadaje temu słowu inny sens.
Dzięki za poświęcenie czasu.