Te warsztaty są częścią kursu Kotlin Bootcamp for Programmers. Najwięcej korzyści przyniesie Ci ukończenie wszystkich ćwiczeń w kolejności. W zależności od swojej wiedzy możesz pominąć niektóre sekcje. Ten kurs jest przeznaczony dla programistów, którzy znają język obiektowy i chcą nauczyć się Kotlin.
Wprowadzenie
W tym laboratorium poznasz wiele przydatnych funkcji Kotlina, w tym pary, kolekcje i funkcje rozszerzeń.
Lekcje w tym kursie nie są powiązane z jedną przykładową aplikacją. Zostały zaprojektowane tak, aby poszerzać Twoją wiedzę, ale jednocześnie były od siebie częściowo niezależne. Dzięki temu możesz przeglądać sekcje, które już znasz. Aby je ze sobą powiązać, w wielu przykładach użyto motywu akwarium. Jeśli chcesz poznać pełną historię akwarium, zapoznaj się z kursem Kotlin Bootcamp for Programmers na platformie Udacity.
Co warto wiedzieć
- Składnia funkcji, klas i metod w języku Kotlin
- Jak korzystać z REPL (Read-Eval-Print Loop) w Kotlinie w IntelliJ IDEA
- Jak utworzyć nową klasę w IntelliJ IDEA i uruchomić program
Czego się nauczysz
- Praca z parami i trójkami
- Więcej informacji o kolekcjach
- Definiowanie i używanie stałych
- Pisanie funkcji rozszerzeń
Jakie zadania wykonasz
- Informacje o parach, trójkach i mapach skrótów w REPL
- Poznaj różne sposoby organizowania stałych
- Napisz funkcję rozszerzającą i właściwość rozszerzającą
W tym ćwiczeniu dowiesz się, czym są pary i trójki oraz jak je rozpakowywać. Pary i trójki to gotowe klasy danych dla 2 lub 3 ogólnych elementów. Może to być przydatne np. wtedy, gdy funkcja ma zwracać więcej niż jedną wartość.
Załóżmy, że masz List
ryb i funkcję isFreshWater()
, która sprawdza, czy ryba jest słodkowodna czy słonowodna. List.partition()
zwraca 2 listy: jedną z produktami, w których warunek to true
, a drugą z produktami, w których warunek to false
.
val twoLists = fish.partition { isFreshWater(it) }
println("freshwater: ${twoLists.first}")
println("saltwater: ${twoLists.second}")
Krok 1. Utwórz kilka par i trójek
- Otwórz REPL (Narzędzia > Kotlin > Kotlin REPL).
- Utwórz parę, przypisując sprzęt do jego zastosowania, a następnie wydrukuj wartości. Parę możesz utworzyć, tworząc wyrażenie łączące 2 wartości, np. 2 ciągi znaków, za pomocą słowa kluczowego
to
, a następnie używając.first
lub.second
, aby odwołać się do każdej wartości.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Utwórz trójkę i wydrukuj ją za pomocą
toString()
, a następnie przekształć ją w listę za pomocątoList()
. Trójkę tworzysz za pomocą symboluTriple()
z 3 wartościami. Aby odwoływać się do poszczególnych wartości, używaj symboli.first
,.second
i.third
.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
W powyższych przykładach wszystkie części pary lub trójki są tego samego typu, ale nie jest to wymagane. Elementy mogą być ciągiem tekstowym, liczbą lub listą, a nawet inną parą lub trójką.
- Utwórz parę, której pierwszy element jest sam w sobie parą.
val equipment2 = ("fish net" to "catching fish") to "equipment"
println("${equipment2.first} is ${equipment2.second}\n")
println("${equipment2.first.second}")
⇒ (fish net, catching fish) is equipment ⇒ catching fish
Krok 2. Rozpakuj niektóre pary i trójki
Rozdzielanie par i trójek na części składowe nazywa się destrukturyzacją. Przypisz parę lub trójkę do odpowiedniej liczby zmiennych, a Kotlin przypisze wartość każdej części w odpowiedniej kolejności.
- Rozpakuj parę i wydrukuj wartości.
val equipment = "fish net" to "catching fish"
val (tool, use) = equipment
println("$tool is used for $use")
⇒ fish net is used for catching fish
- Rozpakuj trójkę i wydrukuj wartości.
val numbers = Triple(6, 9, 42)
val (n1, n2, n3) = numbers
println("$n1 $n2 $n3")
⇒ 6 9 42
Pamiętaj, że rozpakowywanie par i trójek działa tak samo jak w przypadku klas danych, co zostało omówione w poprzednim laboratorium.
Z tego zadania dowiesz się więcej o kolekcjach, w tym o listach, oraz o nowym typie kolekcji – mapach skrótów.
Krok 1. Dowiedz się więcej o listach
- Listy i listy modyfikowalne zostały omówione w poprzedniej lekcji. Są one bardzo przydatną strukturą danych, dlatego Kotlin udostępnia wiele wbudowanych funkcji do obsługi list. Zapoznaj się z tą częściową listą funkcji dotyczących list. Pełne listy znajdziesz w dokumentacji Kotlin dotyczącej
List
iMutableList
.
Funkcja | Purpose |
| Dodaj element do listy modyfikowalnej. |
| Usuń element z listy modyfikowalnej. |
| Zwraca kopię listy z elementami w odwrotnej kolejności. |
| Zwraca wartość |
| Zwraca część listy, od pierwszego indeksu do drugiego indeksu (bez niego). |
- Nadal pracując w REPL, utwórz listę liczb i wywołaj na niej funkcję
sum()
. Sumuje wszystkie elementy.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Utwórz listę ciągów tekstowych i oblicz sumę elementów na liście.
val list2 = listOf("a", "bbb", "cc")
println(list2.sum())
⇒ error: none of the following functions can be called with the arguments supplied:
- Jeśli element nie jest czymś, co
List
potrafi zsumować bezpośrednio, np. ciągiem znaków, możesz określić sposób sumowania za pomocą funkcji.sumBy()
z funkcją lambda, np. aby sumować według długości każdego ciągu znaków. Domyślna nazwa argumentu lambda toit
, ait
odnosi się do każdego elementu listy podczas jej przeglądania.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Listy mają wiele innych zastosowań. Aby sprawdzić dostępne funkcje, utwórz listę w IntelliJ IDEA, dodaj kropkę, a potem przejrzyj listę autouzupełniania w etykiecie. Działa to w przypadku każdego obiektu. Wypróbuj go na liście.
- Wybierz z listy
listIterator()
, a następnie przejrzyj listę z instrukcjąfor
i wydrukuj wszystkie elementy oddzielone spacjami.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Krok 2. Wypróbuj mapy skrótów
W języku Kotlin możesz zmapować niemal wszystko na wszystko inne za pomocą funkcji hashMapOf()
. Mapy haszujące przypominają listę par, w której pierwsza wartość pełni funkcję klucza.
- Utwórz mapę mieszającą, która będzie dopasowywać objawy (klucze) do chorób ryb (wartości).
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Następnie możesz pobrać wartość choroby na podstawie klucza objawu, używając
get()
lub krótszych nawiasów kwadratowych[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Spróbuj podać objaw, którego nie ma na mapie.
println(cures["scale loss"])
⇒ null
Jeśli klucza nie ma na mapie, próba zwrócenia pasującej choroby zwraca wartość null
. W zależności od danych mapy często zdarza się, że nie ma dopasowania do możliwego klucza. W takich przypadkach Kotlin udostępnia funkcję getOrDefault()
.
- Spróbuj wyszukać klucz, który nie ma odpowiednika, używając znaku
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Jeśli chcesz zrobić coś więcej niż tylko zwrócić wartość, Kotlin udostępnia funkcję getOrElse()
.
- Zmień kod, aby używać
getOrElse()
zamiastgetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
Zamiast zwracać prostą wartość domyślną, wykonywany jest kod znajdujący się między nawiasami klamrowymi {}
. W tym przykładzie else
zwraca po prostu ciąg znaków, ale może też wyszukiwać stronę internetową z lekarstwem i ją zwracać.
Podobnie jak w przypadku mutableListOf
możesz też utworzyć mutableMapOf
. Mapa modyfikowalna umożliwia dodawanie i usuwanie elementów. Zmienne oznaczają, że można je zmieniać, a niezmienne, że nie można.
- Utwórz mapę asortymentu, którą można modyfikować, mapując ciąg znaków sprzętu na liczbę elementów. Utwórz go, dodając do niego siatkę na ryby, a następnie dodaj do asortymentu 3 czyściki do akwarium za pomocą ikony
put()
i usuń siatkę na ryby za pomocą ikonyremove()
.
val inventory = mutableMapOf("fish net" to 1)
inventory.put("tank scrubber", 3)
println(inventory.toString())
inventory.remove("fish net")
println(inventory.toString())
⇒ {fish net=1, tank scrubber=3}{tank scrubber=3}
W tym ćwiczeniu dowiesz się, czym są stałe w Kotlinie i jak je porządkować.
Krok 1. Dowiedz się więcej o różnicach między const a val
- W REPL spróbuj utworzyć stałą liczbową. W języku Kotlin możesz tworzyć stałe najwyższego poziomu i przypisywać im wartość w czasie kompilacji za pomocą symbolu
const val
.
const val rocks = 3
Wartość jest przypisywana i nie można jej zmienić, co przypomina deklarowanie zwykłej val
. Czym różni się wersja const val
od val
? Wartość const val
jest określana w czasie kompilacji, a wartość val
– podczas wykonywania programu, co oznacza, że val
może być przypisana przez funkcję w czasie działania.
Oznacza to, że do zmiennej val
można przypisać wartość z funkcji, ale do zmiennej const val
nie.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
Poza tym adnotacja const val
działa tylko na najwyższym poziomie i w klasach singleton zadeklarowanych za pomocą adnotacji object
, a nie w zwykłych klasach. Możesz go użyć do utworzenia pliku lub obiektu singleton, który zawiera tylko stałe, i w razie potrzeby importować je.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Krok 2. Utwórz obiekt towarzyszący
Kotlin nie ma koncepcji stałych na poziomie klasy.
Aby zdefiniować stałe w klasie, musisz umieścić je w obiektach towarzyszących zadeklarowanych za pomocą słowa kluczowego companion
. Obiekt towarzyszący to w zasadzie pojedynczy obiekt w klasie.
- Utwórz klasę z obiektem towarzyszącym zawierającym stałą tekstową.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
Podstawowa różnica między obiektami towarzyszącymi a zwykłymi obiektami polega na tym, że:
- Obiekty towarzyszące są inicjowane przez statyczny konstruktor klasy zawierającej, czyli są tworzone w momencie tworzenia obiektu.
- Zwykłe obiekty są inicjowane leniwie przy pierwszym dostępie do nich, czyli gdy są używane po raz pierwszy.
Jest ich więcej, ale na razie wystarczy, że stałe należy umieszczać w klasach w obiekcie towarzyszącym.
W tym ćwiczeniu dowiesz się, jak rozszerzać działanie klas. Bardzo często pisze się funkcje narzędziowe, aby rozszerzyć działanie klasy. Kotlin udostępnia wygodną składnię do deklarowania tych funkcji narzędziowych: funkcji rozszerzających.
Funkcje rozszerzeń umożliwiają dodawanie funkcji do istniejącej klasy bez konieczności uzyskiwania dostępu do jej kodu źródłowego. Możesz je na przykład zadeklarować w pliku Extensions.kt, który jest częścią pakietu. Nie modyfikuje to klasy, ale umożliwia używanie notacji kropkowej podczas wywoływania funkcji na obiektach tej klasy.
Krok 1. Napisz funkcję rozszerzenia
- Nadal pracując w REPL, napisz prostą funkcję rozszerzenia
hasSpaces()
, która sprawdzi, czy ciąg znaków zawiera spacje. Nazwa funkcji jest poprzedzona nazwą klasy, na której działa. Wewnątrz funkcjithis
odnosi się do obiektu, na którym jest wywoływana, ait
odnosi się do iteratora w wywołaniufind()
.
fun String.hasSpaces(): Boolean {
val found = this.find { it == ' ' }
return found != null
}
println("Does it have spaces?".hasSpaces())
⇒ true
- Możesz uprościć funkcję
hasSpaces()
. Symbolthis
nie jest wyraźnie potrzebny, a funkcję można sprowadzić do jednego wyrażenia i zwrócić, więc nie są też potrzebne otaczające je nawiasy klamrowe{}
.
fun String.hasSpaces() = find { it == ' ' } != null
Krok 2. Poznaj ograniczenia rozszerzeń
Funkcje rozszerzające mają dostęp tylko do publicznego interfejsu API klasy, którą rozszerzają. Do zmiennych, które są private
, nie można uzyskać dostępu.
- Spróbuj dodać funkcje rozszerzeń do usługi oznaczonej jako
private
.
class AquariumPlant(val color: String, private val size: Int)
fun AquariumPlant.isRed() = color == "red" // OK
fun AquariumPlant.isBig() = size > 50 // gives error
⇒ error: cannot access 'size': it is private in 'AquariumPlant'
- Sprawdź poniższy kod i określ, co zostanie wydrukowane.
open class AquariumPlant(val color: String, private val size: Int)
class GreenLeafyPlant(size: Int) : AquariumPlant("green", size)
fun AquariumPlant.print() = println("AquariumPlant")
fun GreenLeafyPlant.print() = println("GreenLeafyPlant")
val plant = GreenLeafyPlant(size = 10)
plant.print()
println("\n")
val aquariumPlant: AquariumPlant = plant
aquariumPlant.print() // what will it print?
⇒ GreenLeafyPlant AquariumPlant
plant.print()
odbitekGreenLeafyPlant
. Możesz oczekiwać, że aquariumPlant.print()
też wydrukuje GreenLeafyPlant
, ponieważ przypisano mu wartość plant
. Typ jest jednak określany w momencie kompilacji, więc wyświetlany jest znak AquariumPlant
.
Krok 3. Dodaj właściwość rozszerzenia
Oprócz funkcji rozszerzających Kotlin umożliwia też dodawanie właściwości rozszerzających. Podobnie jak w przypadku funkcji rozszerzających, określasz klasę, którą rozszerzasz, a następnie dodajesz kropkę i nazwę właściwości.
- Nadal pracując w REPL, dodaj właściwość rozszerzenia
isGreen
doAquariumPlant
, która ma wartośćtrue
, jeśli kolor jest zielony.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
Do właściwości isGreen
można uzyskać dostęp tak samo jak do zwykłej właściwości. Gdy to zrobisz, wywoływana jest funkcja pobierająca wartość isGreen
.
- Wyświetl właściwość
isGreen
zmiennejaquariumPlant
i sprawdź wynik.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Krok 4. Dowiedz się więcej o odbiorcach dopuszczających wartość null
Klasa, którą rozszerzasz, jest nazywana odbiorcą i można ją ustawić jako dopuszczającą wartość null. Jeśli to zrobisz, zmienna this
użyta w treści może mieć wartość null
, więc pamiętaj, aby to sprawdzić. Warto użyć odbiornika dopuszczającego wartość null, jeśli oczekujesz, że wywołujący będą chcieli wywołać metodę rozszerzającą na zmiennych dopuszczających wartość null lub jeśli chcesz zapewnić domyślne działanie, gdy funkcja jest stosowana do null
.
- Nadal pracując w REPL, zdefiniuj metodę
pull()
, która przyjmuje odbiornik dopuszczający wartość null. Jest to oznaczone znakiem zapytania?
po typie, przed kropką. W treści możesz sprawdzić, czythis
nie jest równenull
, używając znaku zapytania z kropką i funkcji apply?.apply.
.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- W tym przypadku po uruchomieniu programu nie pojawią się żadne dane wyjściowe. Ponieważ
plant
tonull
, wewnętrzna funkcjaprintln()
nie jest wywoływana.
Funkcje rozszerzające są bardzo przydatne, a większość biblioteki standardowej Kotlina jest zaimplementowana jako funkcje rozszerzające.
Z tej lekcji dowiedziałeś się więcej o kolekcjach i stałych oraz poznałeś możliwości funkcji i właściwości rozszerzeń.
- Pary i trójki mogą służyć do zwracania więcej niż jednej wartości z funkcji. Na przykład:
val twoLists = fish.partition { isFreshWater(it) }
- Kotlin ma wiele przydatnych funkcji do
List
, takich jakreversed()
,contains()
isubList()
. - Za pomocą znaku
HashMap
można mapować klucze na wartości. Na przykład:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Deklaruj stałe czasu kompilacji za pomocą słowa kluczowego
const
. Możesz umieścić je na najwyższym poziomie, uporządkować w obiekcie singleton lub umieścić w obiekcie towarzyszącym. - Obiekt towarzyszący to obiekt singleton w definicji klasy, zdefiniowany za pomocą słowa kluczowego
companion
. - Funkcje i właściwości rozszerzenia mogą dodawać funkcje do klasy. Na przykład:
fun String.hasSpaces() = find { it == ' ' } != null
- Odbiornik dopuszczający wartość null umożliwia tworzenie rozszerzeń klasy, które mogą być
null
. Operatora?.
można połączyć z operatoremapply
, aby przed wykonaniem kodu sprawdzić, czy występujenull
. Na przykład:this?.apply { println("removing $this") }
Dokumentacja języka Kotlin
Jeśli chcesz uzyskać więcej informacji na dowolny temat w tym kursie lub jeśli napotkasz trudności, najlepszym punktem wyjścia będzie strona https://kotlinlang.org.
Pair
Triple
List
MutableList
HashMap
- Obiekty towarzyszące
- Rozszerzenia
- Odbiornik dopuszczający wartość null
Samouczki dotyczące języka Kotlin
Witryna https://try.kotlinlang.org zawiera rozbudowane samouczki Kotlin Koans, internetowy interpreter i pełny zestaw dokumentacji z przykładami.
Kurs Udacity
Aby obejrzeć kurs Udacity na ten temat, zobacz Kotlin Bootcamp for Programmers (w języku angielskim).
IntelliJ IDEA
Dokumentację IntelliJ IDEA znajdziesz w witrynie JetBrains.
W tej sekcji znajdziesz listę możliwych zadań domowych dla uczniów, którzy wykonują ten moduł w ramach kursu prowadzonego przez instruktora. Nauczyciel musi:
- W razie potrzeby przypisz pracę domową.
- Poinformuj uczniów, jak przesyłać projekty.
- Oceń zadania domowe.
Instruktorzy mogą korzystać z tych sugestii w dowolnym zakresie i mogą zadawać inne zadania domowe, które uznają za odpowiednie.
Jeśli wykonujesz ten kurs samodzielnie, możesz użyć tych zadań domowych, aby sprawdzić swoją wiedzę.
Odpowiedz na te pytania
Pytanie 1
Która z tych funkcji zwraca kopię listy?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Pytanie 2
Która z tych funkcji rozszerzających w class AquariumPlant(val color: String, val size: Int, private val cost: Double, val leafy: Boolean)
spowoduje błąd kompilatora?
▢ fun AquariumPlant.isRed() = color == "red"
▢ fun AquariumPlant.isBig() = size > 45
▢ fun AquariumPlant.isExpensive() = cost > 10.00
▢ fun AquariumPlant.isNotLeafy() = leafy == false
Pytanie 3
Które z tych miejsc NIE jest miejscem, w którym można zdefiniować stałe za pomocą const val
?
▢ na najwyższym poziomie pliku
▢ na zajęciach regularnych,
▢ w obiektach singleton
▢ w obiektach towarzyszących
Przejdź do następnej lekcji:
Omówienie kursu, w tym linki do innych ćwiczeń, znajdziesz w artykule „Kotlin Bootcamp for Programmers: Welcome to the course” (w języku angielskim).