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.

"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).

Pętla "for" w języku Kotlin dla tablicy Sprawdzenie czy element zawiera się w tablicy za pomocą słowa kluczowego "in" w języku Kotlin

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.

PODOBNE ARTYKUŁY