Te ćwiczenia są częścią kursu Kotlin dla programistów. Skorzystaj z tego kursu, jeśli będziesz wykonywać kolejno kilka ćwiczeń z programowania. W zależności od Twojej wiedzy możesz mieć dostęp do niektórych sekcji. Ten kurs jest przeznaczony dla programistów, którzy znają język zorientowany na obiekty i chcą się dowiedzieć Kotlina.
Wstęp
Dzięki nim dowiesz się wielu przydatnych funkcji Kotlina, takich jak pary, kolekcje i funkcje rozszerzeń.
Lekcje nie obejmują jednej przykładowej aplikacji, ale mają one na celu pogłębianie wiedzy. Między innymi należy jednak pamiętać o zależności od siebie. W połączeniu wiele z przykładów wykorzystuje motyw akwariowy. A jeśli chcesz zobaczyć pełne informacje o oceanarium, zapoznaj się z kursem Kotlin Bootcamp for Programmers (Ukocity) na platformie Udacity.
Co musisz wiedzieć
- Składnia funkcji, klas i metod Kotlin
- Jak pracować z Kotlin&s REPL (Read-Eval-Print Loop) w IntelliJ IDEA
- Jak utworzyć nową klasę w IntelliJ IDEA i uruchomić program
Czego się nauczysz
- Jak pracować z parami i potrójnie
- Więcej informacji o kolekcjach
- Definiowanie i używanie stałych
- Funkcje rozszerzeń tekstu
Jakie zadania wykonasz:
- Dowiedz się więcej o parach, potrójnych i mapach haszujących w programie REPL
- Poznaj różne sposoby organizowania stałych
- Tworzenie funkcji rozszerzenia i właściwości rozszerzenia
W tym zadaniu poznasz pary i trójki oraz zniszczysz je. Pary i potrójne to gotowe klasy danych dla dwóch lub trzech produktów ogólnych. Może to być przydatne na przykład wtedy, gdy funkcja zwraca więcej niż jedną wartość.
Załóżmy, że masz List
ryby i funkcję isFreshWater()
, która sprawdza, czy jest to ryba słodkowodna lub słonowodna. List.partition()
zwraca dwie listy: jedną z elementami o warunku true
, a drugą z elementami, 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 trzy
- Otwórz REPL (Narzędzia > Kotlin > Kotlin REPL).
- Utwórz parę, łącząc instrument z tym, do czego jest używany, i wydrukuj wartości. Można utworzyć parę, tworząc wyrażenie łączące 2 wartości, np. 2 ciągi, ze słowem kluczowym
to
, a następnie używając.first
lub.second
do odwoływania się do każdej z nich.
val equipment = "fish net" to "catching fish"
println("${equipment.first} used for ${equipment.second}")
⇒ fish net used for catching fish
- Utwórz potrójny wydruk i użyj go w systemie
toString()
, a następnie przekonwertuj go na listę za pomocą atrybututoList()
. Tworzysz potrójnie elementTriple()
za pomocą 3 wartości. Użyj.first
,.second
i.third
, aby odwołać się do każdej wartości.
val numbers = Triple(6, 9, 42)
println(numbers.toString())
println(numbers.toList())
⇒ (6, 9, 42) [6, 9, 42]
W przykładach powyżej zastosowano ten sam typ wszystkich części pary lub potrójnego układu, ale nie jest to wymagane. Częścią może być na przykład ciąg, liczba lub lista – nawet inna para lub potrójna para.
- Utwórz parę, w której pierwsza jej część jest 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. Zniszcz niektóre pary i potrójne elementy
Dzielenie par na pary na potrójne jest nazywane niszczeniem. Przypisz parę lub potrójnie do odpowiedniej liczby zmiennych, a Kotlin przypisze wartość każdej części w określonej kolejności.
- Zdeformuj 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
- Zdeformuj potrójnie 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 niszczenie par i potrójnie działa tak samo jak w przypadku klas danych, które były opisane w poprzednim ćwiczeniu z programowania.
W tym zadaniu dowiesz się więcej o kolekcjach (w tym listach) oraz o nowym typie kolekcji (mapy).
Krok 1. Więcej informacji o listach
- Listy i listy możliwe do zmodyfikowania zostały wprowadzone we wcześniejszej lekcji. To bardzo przydatna struktura danych, więc Kotlin udostępnia wiele funkcji wbudowanych dla list. Przejrzyj tę częściową listę funkcji list. Pełne informacje znajdziesz w dokumentacji Kotlin dla
List
iMutableList
.
Funkcja | Cel |
| Dodaj element do listy zmiennej. |
| Usuń element z listy, którą można zmienić. |
| Zwraca kopię listy z elementami w odwrotnej kolejności. |
| Zwróć wartość |
| Zwróć część listy od pierwszego indeksu do drugiego, bez uwzględnienia drugiego. |
- Pracując w REPL, utwórz listę numerów i wywołaj ją
sum()
. To suma wszystkich elementów.
val list = listOf(1, 5, 3, 4)
println(list.sum())
⇒ 13
- Utwórz listę ciągów znaków i zsumuj ją.
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 dokładny
List
, np. ciąg znaków, możesz określić sposób jego zsumowania za pomocą funkcji.sumBy()
z funkcją lambdy, na przykład aby zsumować długość każdego ciągu. Domyślna nazwa argumentu lambdy toit
. W tym miejscuit
odwołuje się do każdego elementu listy, ponieważ przewinięta jest lista.
val list2 = listOf("a", "bbb", "cc")
println(list2.sumBy { it.length })
⇒ 6
- Z listami można robić znacznie więcej. Aby sprawdzić, czy funkcja jest dostępna, należy utworzyć listę w IntelliJ IDEA, dodać kropkę, a następnie sprawdzić listę autouzupełniania w etykietce. Sprawdza się to w przypadku wszystkich obiektów. Spróbuj z listą.
- Wybierz
listIterator()
z listy, a następnie przejrzyj listę przy użyciu instrukcjifor
i wydrukuj wszystkie elementy rozdzielone spacjami.
val list2 = listOf("a", "bbb", "cc")
for (s in list2.listIterator()) {
println("$s ")
}
⇒ a bbb cc
Krok 2. Wypróbuj mapy hasz
Za pomocą usługi hashMapOf()
w Kotlin możesz niemal zmapować wszystko, co tylko zechcesz. Mapy haszowania są jak lista par, w której pierwsza wartość jest kluczem.
- Utwórz mapę z haszowaniem odpowiadającą wartościom, objawom i kluczowym chorobom ryb.
val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Następnie możesz pobrać wartość choroby na podstawie objawu, używając
get()
lub nawet krótszych nawiasów kwadratowych[]
.
println(cures.get("white spots"))
⇒ Ich
println(cures["red sores"])
⇒ hole disease
- Spróbuj określić objaw, który nie występuje na mapie.
println(cures["scale loss"])
⇒ null
Jeśli klucz nie znajduje się na mapie, próba zwrócenia pasującej choroby zwraca null
. W zależności od danych mapy może się zdarzyć, że nie uda się znaleźć dopasowania do możliwego klucza. W takich przypadkach Kotlin udostępnia funkcję getOrDefault()
.
- Spróbuj wyszukać klucz, który nie pasuje, używając
getOrDefault()
.
println(cures.getOrDefault("bloating", "sorry, I don't know"))
⇒ sorry, I don't know
Jeśli potrzebujesz czegoś więcej niż tylko zwrócić wartość, Kotlin udostępnia funkcję getOrElse()
.
- Zmień kod, aby używać kodu
getOrElse()
zamiastgetOrDefault()
.
println(cures.getOrElse("bloating") {"No cure for this"})
⇒ No cure for this
Zamiast zwracania prostej wartości domyślnej tworzony jest kod między nawiasami klamrowymi {}
. W tym przykładzie else
po prostu zwraca ciąg znaków, ale może to być równie wyrafinowane jak znalezienie strony z lekami i zwrotem.
Podobnie jak mutableListOf
, możesz też zrobić mutableMapOf
. Zmienna mapa umożliwia umieszczanie i usuwanie elementów. Wartość zmienne oznacza po prostu możliwość zmiany, a ciągłe to brak możliwości zmiany.
- Utwórz mapę zasobów reklamowych, którą możesz zmieniać, mapując ciąg znaków na sprzęt na liczbę produktów. Utwórz ją z siatką rybną, a następnie dodaj 3 zbiorniki do akwariów w usłudze
put()
i usuń sieć rybną zremove()
.
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 zadaniu poznamy stałe w Kotlinie i poznamy różne sposoby ich organizowania.
Krok 1. Porównanie kontr. i wal.
- W REPL spróbuj utworzyć stałą liczbową. W Kotlin możesz utworzyć stałe najwyższego poziomu i przypisać im wartość w czasie kompilacji za pomocą funkcji
const val
.
const val rocks = 3
Wartość jest przypisana i nie można jej zmienić, co brzmi jak deklarowanie zwykłego typu val
. Jaka jest różnica między const val
a val
? Wartość parametru const val
jest określana w momencie kompilowania, gdzie wartość val
jest określana podczas wykonywania programu, co oznacza, że funkcję val
można przypisać w trakcie działania.
Oznacza to, że val
może mieć przypisaną wartość z funkcji, ale const val
nie może tego zrobić.
val value1 = complexFunctionCall() // OK
const val CONSTANT1 = complexFunctionCall() // NOT ok
Oprócz tego const val
działa tylko na najwyższym poziomie i w klasach pojedynczego typu zadeklarowanych jako object
, a nie w standardowych. Za jego pomocą możesz utworzyć plik lub pojedynczy obiekt, który zawiera tylko stałe, i zaimportować je w razie potrzeby.
object Constants {
const val CONSTANT2 = "object constant"
}
val foo = Constants.CONSTANT2
Krok 2. Utwórz obiekt towarzyszący
Kotlin nie ma pojęcia stałych stałych na poziomie klasy.
Aby zdefiniować stałe w klasie, musisz owinąć je w obiekty towarzyszące zadeklarowane za pomocą słowa kluczowego companion
. Obiekt towarzyszący to w zasadzie obiekt pojedynczego tonu.
- Utwórz klasę z obiektem towarzyszącym zawierającym stały ciąg znaków.
class MyClass {
companion object {
const val CONSTANT3 = "constant in companion"
}
}
Podstawowa różnica między obiektami towarzyszącymi a zwykłymi obiektami to:
- Obiekty towarzyszące są inicjowane na podstawie statycznego konstruktora klasy zawierającej, co oznacza, że są tworzone podczas tworzenia obiektu.
- Obiekty zwykłe są leniwie inicjowane przy pierwszym dostępie do danego obiektu, czyli przy pierwszym użyciu.
To jeszcze nie wszystko, ale teraz musisz tylko pamiętać o umieszczeniu stałych w klasach obiektów towarzyszących.
W tym zadaniu nauczysz się rozszerzać zachowanie zajęć. Zapisywanie funkcji użytkowych w celu rozszerzenia zakresu klasy jest bardzo powszechne. Kotlin udostępnia wygodną składnię tych funkcji:
Funkcje rozszerzeń umożliwiają dodawanie funkcji do istniejących klas bez konieczności otwierania kodu źródłowego. Możesz na przykład zadeklarować je w pliku Extensions.kt, który jest częścią Twojego pakietu. Nie powoduje to zmodyfikowania zajęć, ale pozwala wywołać notację w postaci kropek w obiektach tej klasy.
Krok 1. Napisz funkcję rozszerzenia
- Nadal w REPL zapisuj prostą funkcję rozszerzenia
hasSpaces()
, aby sprawdzić, czy ciąg zawiera spacje. Nazwa funkcji jest poprzedzona klasą, w której działa. W ramach funkcjithis
odnosi się do obiektu, który jest wywoływany, ait
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()
.this
nie jest jawnie potrzebny, a funkcję można zmniejszyć do jednego wyrażenia i zwrócić, więc nawias klamrowy{}
wokół niego nie jest potrzebny.
fun String.hasSpaces() = find { it == ' ' } != null
Krok 2. Poznaj ograniczenia rozszerzeń
Funkcje rozszerzeń mają dostęp tylko do publicznego interfejsu API klasy, której wersję można rozszerzyć. Nie można uzyskać dostępu do zmiennych private
.
- Spróbuj dodać funkcje rozszerzeń do usługi oznaczonej etykietą
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'
- Przejrzyj kod i zastanów się, 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()
odbitka GreenLeafyPlant
. aquariumPlant.print()
może też wydrukować plik GreenLeafyPlant
, ponieważ ma przypisaną wartość plant
. Jednak typ jest usuwany w momencie kompilacji, więc AquariumPlant
zostaje wydrukowany.
Krok 3. Dodaj właściwość rozszerzenia
Oprócz funkcji rozszerzenia Kotlin umożliwia też dodawanie właściwości rozszerzenia. Podobnie jak w przypadku funkcji rozszerzenia, klasy, których wartość rozszerzasz, podajesz kropkę, po której następuje nazwa właściwości.
- Nadal używaj rozszerzenia REPL, dodaj właściwość rozszerzenia
isGreen
doAquariumPlant
, która ma kolortrue
, jeśli kolor jest zielony.
val AquariumPlant.isGreen: Boolean
get() = color == "green"
Dostęp do właściwości isGreen
można uzyskać tak samo jak standardową usługę. Po uzyskaniu dostępu do metody pobierany jest element pobierający isGreen
, który pobiera wartość.
- Wydrukuj właściwość
isGreen
zmiennejaquariumPlant
i zaobserwuj wynik.
aquariumPlant.isGreen
⇒ res4: kotlin.Boolean = true
Krok 4. Poznaj adresatów o wartości null
Rozszerzona klasa jest nazywana odbiornikiem i możesz ją przypisać do wartości null. W takim przypadku zmienna this
użyta w treści może mieć wartość null
, więc sprawdź, czy jest odpowiednia. Użyj odbiornika o wartości null, jeśli oczekujesz, że rozmówcy będą wywoływać metodę rozszerzenia w przypadku zmiennych null lub jeśli chcesz zapewnić domyślne działanie, gdy funkcja jest stosowana do funkcji null
.
- Nadal w środowisku REPL określ metodę
pull()
, która przyjmuje odbiornik o wartości null. Wskazuje to znak zapytania?
po typie przed kropką. Aby sprawdzić, czy elementthis
nie jestnull
, użyj?.apply.
fun AquariumPlant?.pull() {
this?.apply {
println("removing $this")
}
}
val plant: AquariumPlant? = null
plant.pull()
- W tym przypadku dane wyjściowe nie będą widoczne po uruchomieniu programu.
plant
tonull
, więc wewnętrzny elementprintln()
nie jest wywoływany.
Funkcje rozszerzeń są bardzo zaawansowane, a większość standardowej biblioteki Kotlin jest zaimplementowana jako funkcje rozszerzeń.
Podczas tej lekcji dowiedzieliśmy się więcej o kolekcjach i stałych oraz zdołaliśmy przedstawić potęgę i funkcje rozszerzeń.
- Pary i trójki mogą służyć do zwracania więcej niż jednej wartości z funkcji. Przykład:
val twoLists = fish.partition { isFreshWater(it) }
- Kotlin ma wiele przydatnych funkcji usługi
List
, takich jakreversed()
,contains()
isubList()
. HashMap
może służyć do mapowania kluczy na wartości. Przykład:val cures = hashMapOf("white spots" to "Ich", "red sores" to "hole disease")
- Zadeklaruj stałe czasy kompilacji za pomocą słowa kluczowego
const
. Możesz umieścić je na najwyższym poziomie, umieścić w pojedynczym obiekcie lub umieścić w elemencie towarzyszącym. - Obiekt towarzyszący to pojedynczy obiekt w definicji klasy zdefiniowany za pomocą słowa kluczowego
companion
. - Funkcje i właściwości rozszerzeń mogą dodawać funkcje do klasy. Przykład:
fun String.hasSpaces() = find { it == ' ' } != null
- Odbiorca null umożliwia tworzenie rozszerzeń, które mogą mieć
null
. Operator?.
można sparować z systememapply
, aby sprawdzać kodnull
przed wykonaniem kodu. Przykład:this?.apply { println("removing $this") }
Dokumentacja Kotlin
Jeśli chcesz dowiedzieć się więcej na dowolny temat dotyczący tego kursu lub nie wiesz, co zrobić, wejdź na https://kotlinlang.org.
Samouczki Kotlin
Na stronie https://try.kotlinlang.org znajdziesz szczegółowe samouczki o nazwie Kotlin Koans, internetowe narzędzie do tłumaczenia, a także kompletny zestaw dokumentacji referencyjnej z przykładami.
Kurs Udacity
Kurs Udacity na ten temat znajdziesz na kursie Kotlin dla programistów.
IntelLIJ IDEA
Dokumentację IntelliJ IDEA znajdziesz na stronie JetBrains.
Ta sekcja zawiera listę możliwych zadań domowych dla uczniów, którzy pracują w ramach tego ćwiczenia w ramach kursu prowadzonego przez nauczyciela. To nauczyciel może wykonać te czynności:
- W razie potrzeby przypisz zadanie domowe.
- Poinformuj uczniów, jak przesyłać zadania domowe.
- Oceń projekty domowe.
Nauczyciele mogą wykorzystać te sugestie tak długo, jak chcą lub chcą, i mogą przypisać dowolne zadanie domowe.
Jeśli samodzielnie wykonujesz te ćwiczenia z programowania, możesz sprawdzić swoją wiedzę w tych zadaniach domowych.
Odpowiedz na te pytania
Pytanie 1
Która z poniższych odpowiedzi zwraca kopię listy?
▢ add()
▢ remove()
▢ reversed()
▢ contains()
Pytanie 2
Która z tych funkcji rozszerzenia 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óra z poniższych pozycji nie jest miejscem, w którym można definiować stałe wartości za pomocą atrybutu const val
?
▢ na najwyższym poziomie pliku
▢ w zwykłych zajęciach
▢ w obiektach Singleton
▢ w obiektach towarzyszących
Przejdź do następnej lekcji:
Omówienie kursu, w tym linki do innych ćwiczeń z programowania, znajdziesz w artykule "Kotlin Bootcamp for Programmers: Witamy na kursie."