Kotlin Bootcamp for Programmers 5.2: Generics

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 dowiesz się, czym są klasy, funkcje i metody generyczne oraz jak działają w Kotlinie.

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 utworzyć nową klasę w IntelliJ IDEA i uruchomić program

Czego się nauczysz

  • Jak pracować z klasami, metodami i funkcjami ogólnymi

Jakie zadania wykonasz

  • Tworzenie klasy ogólnej i dodawanie ograniczeń
  • Tworzenie typów inout
  • Tworzenie funkcji, metod i funkcji rozszerzających

Wprowadzenie do typów generycznych

Kotlin, podobnie jak wiele języków programowania, ma typy ogólne. Typ ogólny umożliwia utworzenie klasy ogólnej, a tym samym zwiększenie jej elastyczności.

Załóżmy, że implementujesz klasę MyList, która zawiera listę elementów. Bez typów ogólnych musiałbyś wdrożyć nową wersję funkcji MyList dla każdego typu: jedną dla Double, jedną dla String i jedną dla Fish. Dzięki typom ogólnym możesz utworzyć listę ogólną, która może zawierać dowolny typ obiektu. To tak, jakby typ był symbolem wieloznacznym, który pasuje do wielu typów.

Aby zdefiniować typ ogólny, umieść T w nawiasach ostrych <T> po nazwie klasy. (Możesz użyć innej litery lub dłuższej nazwy, ale w przypadku typu ogólnego przyjęło się używać litery T).

class MyList<T> {
    fun get(pos: Int): T {
        TODO("implement")
    }
    fun addItem(item: T) {}
}

Możesz odwoływać się do T tak, jakby był to zwykły typ. Typem zwracanym funkcji get() jest T, a parametr funkcji addItem() jest typu T. Oczywiście listy ogólne są bardzo przydatne, dlatego klasa List jest wbudowana w język Kotlin.

Krok 1. Utwórz hierarchię typów

W tym kroku utworzysz klasy, które wykorzystasz w następnym kroku. Podklasy zostały omówione w poprzednim laboratorium, ale tutaj znajdziesz krótkie podsumowanie.

  1. Aby przykład był bardziej przejrzysty, utwórz nowy pakiet w folderze src i nadaj mu nazwę generics.
  2. W pakiecie generics utwórz nowy plik Aquarium.kt. Dzięki temu możesz ponownie zdefiniować elementy, używając tych samych nazw, bez konfliktów. Pozostała część kodu do tego laboratorium kodu znajduje się w tym pliku.
  3. Utwórz hierarchię typów zaopatrzenia w wodę. Zacznij od utworzenia WaterSupplyklasyopen, aby można było utworzyć jej podklasę.
  4. Dodaj parametr logiczny var, needsProcessing. Spowoduje to automatyczne utworzenie usługi z możliwością zmiany oraz metod get i set.
  5. Utwórz podklasę TapWater, która rozszerza klasę WaterSupply, i przekaż true jako needsProcessing, ponieważ woda z kranu zawiera dodatki, które są szkodliwe dla ryb.
  6. TapWater zdefiniuj funkcję o nazwie addChemicalCleaners(), która po oczyszczeniu wody ustawia wartość needsProcessing na false. Właściwość needsProcessing można ustawić z poziomu klasy TapWater, ponieważ domyślnie jest ona public i dostępna dla podklas. Oto gotowy kod.
package generics

open class WaterSupply(var needsProcessing: Boolean)

class TapWater : WaterSupply(true) {
   fun addChemicalCleaners() {
       needsProcessing = false
   }
}
  1. Utwórz 2 kolejne podklasy klasy WaterSupply o nazwach FishStoreWaterLakeWater. FishStoreWater nie wymaga przetwarzania, ale LakeWater musi być filtrowany metodą filter(). Po odfiltrowaniu nie trzeba go ponownie przetwarzać, więc w filter() ustaw needsProcessing = false.
class FishStoreWater : WaterSupply(false)

class LakeWater : WaterSupply(true) {
   fun filter() {
       needsProcessing = false
   }
}

Jeśli potrzebujesz dodatkowych informacji, zapoznaj się z poprzednią lekcją o dziedziczeniu w Kotlinie.

Krok 2. Utwórz klasę ogólną

W tym kroku zmodyfikujesz klasę Aquarium, aby obsługiwała różne rodzaje źródeł wody.

  1. W pliku Aquarium.kt zdefiniuj klasę Aquarium, a po nazwie klasy w nawiasach podaj <T>.
  2. Dodaj do Aquarium niezmienną właściwość waterSupply typu T.
class Aquarium<T>(val waterSupply: T)
  1. Napisz funkcję o nazwie genericsExample(). Nie jest to część klasy, więc może znajdować się na najwyższym poziomie pliku, podobnie jak funkcja main() lub definicje klas. W funkcji utwórz Aquarium i przekaż do niej WaterSupply. Parametr waterSupply jest ogólny, więc musisz określić jego typ w nawiasach ostrych <>.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
}
  1. genericsExample() Twój kod może uzyskać dostęp do waterSupply akwarium. Ponieważ jest to typ TapWater, możesz wywołać addChemicalCleaners() bez rzutowania typów.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. Podczas tworzenia obiektu Aquarium możesz usunąć nawiasy kątowe i to, co się między nimi znajduje, ponieważ Kotlin ma wnioskowanie o typach. Dlatego podczas tworzenia instancji nie musisz wpisywać TapWater dwa razy. Typ można wywnioskować z argumentu funkcji Aquarium. Nadal będzie ona tworzyć obiekt Aquarium typu TapWater.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    aquarium.waterSupply.addChemicalCleaners()
}
  1. Aby sprawdzić, co się dzieje, wydrukuj needsProcessing przed wywołaniem funkcji addChemicalCleaners() i po nim. Poniżej znajdziesz gotową funkcję.
fun genericsExample() {
    val aquarium = Aquarium<TapWater>(TapWater())
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
    aquarium.waterSupply.addChemicalCleaners()
    println("water needs processing: ${aquarium.waterSupply.needsProcessing}")
}
  1. Dodaj funkcję main(), aby wywołać funkcję genericsExample(), a następnie uruchom program i sprawdź wynik.
fun main() {
    genericsExample()
}
⇒ water needs processing: true
water needs processing: false

Krok 3. Uściślij pytanie

Oznacza to, że możesz przekazać prawie wszystko, a czasami jest to problem. W tym kroku Aquariumklasa staje się bardziej szczegółowa w odniesieniu do tego, co można w niej umieścić.

  1. genericsExample() utwórz Aquarium, przekazując ciąg znaków dla waterSupply, a następnie wydrukuj właściwość waterSupply akwarium.
fun genericsExample() {
    val aquarium2 = Aquarium("string")
    println(aquarium2.waterSupply)
}
  1. Uruchom program i sprawdź wynik.
⇒ string

Wynikiem jest przekazany ciąg znaków, ponieważ funkcja Aquarium nie nakłada żadnych ograniczeń na typ T.. Można przekazać dowolny typ, w tym String.

  1. W funkcji genericsExample() utwórz kolejną funkcję Aquarium, przekazując wartość null jako argument waterSupply. Jeśli waterSupply ma wartość null, wydrukuj "waterSupply is null".
fun genericsExample() {
    val aquarium3 = Aquarium(null)
    if (aquarium3.waterSupply == null) {
        println("waterSupply is null")
    }
}
  1. Uruchom program i sprawdź wynik.
⇒ waterSupply is null

Dlaczego podczas tworzenia Aquarium można przekazać wartość null? Jest to możliwe, ponieważ domyślnie T oznacza typ dopuszczający wartość null Any?, czyli typ na szczycie hierarchii typów. Poniższy tekst jest odpowiednikiem tego, co zostało wcześniej wpisane.

class Aquarium<T: Any?>(val waterSupply: T)
  1. Aby nie zezwalać na przekazywanie null, usuń ? po Any, aby jawnie określić typ T jako Any.
class Aquarium<T: Any>(val waterSupply: T)

W tym kontekście Any jest nazywany ogólnym ograniczeniem. Oznacza to, że w przypadku parametru T można przekazać dowolny typ, o ile nie jest to null.

  1. Chcesz mieć pewność, że do T można przekazać tylko WaterSupply (lub jedną z jego podklas). Zastąp symbol Any symbolem WaterSupply, aby zdefiniować bardziej szczegółowe ograniczenie ogólne.
class Aquarium<T: WaterSupply>(val waterSupply: T)

Krok 4. Dodaj więcej sprawdzania

W tym kroku dowiesz się więcej o funkcji check(), która pomoże Ci upewnić się, że kod działa zgodnie z oczekiwaniami. Funkcja check() jest standardową funkcją biblioteczną w Kotlinie. Działa jako asercja i zwraca błąd IllegalStateException, jeśli argument ma wartość false.

  1. Dodaj do klasy Aquarium metodę addWater(), aby dodać wodę, z parametrem check(), który sprawi, że nie trzeba będzie jej wcześniej przetwarzać.
class Aquarium<T: WaterSupply>(val waterSupply: T) {
    fun addWater() {
        check(!waterSupply.needsProcessing) { "water supply needs processing first" }
        println("adding water from $waterSupply")
    }    
}

W tym przypadku, jeśli needsProcessing ma wartość true, funkcja check() zgłosi wyjątek.

  1. genericsExample() dodaj kod, aby utworzyć AquariumLakeWater, a następnie dodaj do niego trochę wody.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.addWater()
}
  1. Uruchom program, a otrzymasz wyjątek, ponieważ woda musi zostać najpierw przefiltrowana.
⇒ Exception in thread "main" java.lang.IllegalStateException: water supply needs processing first
        at Aquarium.generics.Aquarium.addWater(Aquarium.kt:21)
  1. Przed dodaniem wody do Aquarium przefiltruj ją. Teraz, gdy uruchomisz program, nie zostanie zgłoszony żaden wyjątek.
fun genericsExample() {
    val aquarium4 = Aquarium(LakeWater())
    aquarium4.waterSupply.filter()
    aquarium4.addWater()
}
⇒ adding water from generics.LakeWater@880ec60

Powyżej znajdziesz podstawowe informacje o typach generycznych. Poniższe zadania obejmują więcej, ale ważna jest koncepcja deklarowania i używania klasy ogólnej z ograniczeniem ogólnym.

W tym ćwiczeniu dowiesz się więcej o typach wejściowych i wyjściowych z typami ogólnymi. Typ in to typ, który można tylko przekazać do klasy, a nie zwrócić. out to typ, który może być zwracany tylko przez klasę.

Spójrz na klasę Aquarium, a zobaczysz, że typ ogólny jest zwracany tylko w przypadku pobierania właściwości waterSupply. Nie ma metod, które przyjmują wartość typu T jako parametr (z wyjątkiem zdefiniowania go w konstruktorze). Kotlin umożliwia definiowanie out typów dokładnie w tym przypadku i może wywnioskować dodatkowe informacje o tym, gdzie można bezpiecznie używać typów. Podobnie możesz zdefiniować typy in dla typów ogólnych, które są tylko przekazywane do metod, a nie zwracane. Dzięki temu Kotlin może przeprowadzać dodatkowe kontrole bezpieczeństwa kodu.

Typy inout to dyrektywy dla systemu typów języka Kotlin. Wyjaśnienie całego systemu typów wykracza poza zakres tego szkolenia (jest dość złożone). Kompilator będzie jednak odpowiednio oznaczać typy, które nie są oznaczone symbolami inout, więc musisz o nich wiedzieć.

Krok 1. Określ typ wyjścia

  1. W klasie Aquarium zmień typ elementu T: WaterSupply na out.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    ...
}
  1. W tym samym pliku, poza klasą, zadeklaruj funkcję addItemTo(), która oczekuje Aquarium typu WaterSupply.
fun addItemTo(aquarium: Aquarium<WaterSupply>) = println("item added")
  1. Zadzwoń z genericsExample() do addItemTo() i uruchom program.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    addItemTo(aquarium)
}
⇒ item added

Kotlin może zagwarantować, że addItemTo() nie wykona żadnych operacji niezgodnych z typem w przypadku typu ogólnego WaterSupply, ponieważ jest on zadeklarowany jako typ out.

  1. Jeśli usuniesz słowo kluczowe out, kompilator zgłosi błąd podczas wywoływania addItemTo(), ponieważ Kotlin nie może zagwarantować, że nie wykonujesz żadnych niebezpiecznych operacji na typie.

Krok 2. Określ typ wejścia

Typ in jest podobny do typu out, ale dotyczy typów ogólnych, które są tylko przekazywane do funkcji, a nie zwracane. Jeśli spróbujesz zwrócić typ in, pojawi się błąd kompilatora. W tym przykładzie zdefiniujesz in jako część interfejsu.

  1. W pliku Aquarium.kt zdefiniuj interfejs Cleaner, który przyjmuje ogólny typ T ograniczony do typu WaterSupply. Ponieważ jest on używany tylko jako argument funkcji clean(), możesz ustawić go jako parametr in.
interface Cleaner<in T: WaterSupply> {
    fun clean(waterSupply: T)
}
  1. Aby użyć interfejsu Cleaner, utwórz klasę TapWaterCleaner, która implementuje interfejs Cleaner do czyszczenia TapWater przez dodanie środków chemicznych.
class TapWaterCleaner : Cleaner<TapWater> {
    override fun clean(waterSupply: TapWater) =   waterSupply.addChemicalCleaners()
}
  1. W klasie Aquarium zaktualizuj addWater(), aby pobrać Cleaner typu T, i oczyść wodę przed jej dodaniem.
class Aquarium<out T: WaterSupply>(val waterSupply: T) {
    fun addWater(cleaner: Cleaner<T>) {
        if (waterSupply.needsProcessing) {
            cleaner.clean(waterSupply)
        }
        println("water added")
    }
}
  1. Zaktualizuj przykładowy kod genericsExample(), aby utworzyć TapWaterCleaner, AquariumTapWater, a następnie dodaj trochę wody za pomocą środka czyszczącego. Będzie używać środka czyszczącego w razie potrzeby.
fun genericsExample() {
    val cleaner = TapWaterCleaner()
    val aquarium = Aquarium(TapWater())
    aquarium.addWater(cleaner)
}

Kotlin użyje informacji o typach inout, aby upewnić się, że kod bezpiecznie korzysta z typów ogólnych. Outin są łatwe do zapamiętania: typy out można przekazywać na zewnątrz jako wartości zwracane, a typy in można przekazywać do wewnątrz jako argumenty.

Jeśli chcesz dowiedzieć się więcej o problemach, które rozwiązują typy wejściowe i wyjściowe, zapoznaj się ze szczegółową dokumentacją.

Z tego zadania dowiesz się, czym są funkcje ogólne i kiedy ich używać. Zwykle warto utworzyć funkcję ogólną, gdy przyjmuje ona argument klasy, która ma typ ogólny.

Krok 1. Utwórz funkcję ogólną

  1. W pliku generics/Aquarium.kt utwórz funkcję isWaterClean(), która przyjmuje argument Aquarium. Musisz określić typ ogólny parametru. Jedną z opcji jest użycie WaterSupply.
fun isWaterClean(aquarium: Aquarium<WaterSupply>) {
   println("aquarium water is clean: ${aquarium.waterSupply.needsProcessing}")
}

Oznacza to jednak, że funkcja Aquarium musi mieć parametr typu out, aby można było ją wywołać. Czasami out lub in jest zbyt restrykcyjne, ponieważ musisz użyć typu zarówno dla danych wejściowych, jak i wyjściowych. Możesz usunąć wymaganie out, uogólniając funkcję.

  1. Aby funkcja była ogólna, umieść nawiasy kątowe po słowie kluczowym fun z ogólnym typem T i dowolnymi ograniczeniami, w tym przypadku WaterSupply. Zmień ograniczenie Aquarium na T zamiast WaterSupply.
fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) {
   println("aquarium water is clean: ${!aquarium.waterSupply.needsProcessing}")
}

T to parametr typu dla isWaterClean(), który jest używany do określania ogólnego typu akwarium. Ten wzorzec jest bardzo powszechny, więc warto poświęcić chwilę na jego przeanalizowanie.

  1. Wywołaj funkcję isWaterClean(), podając typ w nawiasach ostrych bezpośrednio po nazwie funkcji i przed nawiasami.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean<TapWater>(aquarium)
}
  1. Ze względu na wnioskowanie o typie z argumentu aquarium typ nie jest potrzebny, więc go usuń. Uruchom program i obserwuj dane wyjściowe.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    isWaterClean(aquarium)
}
⇒ aquarium water is clean: false

Krok 2. Utwórz metodę ogólną z typem konkretnym

Możesz też używać funkcji ogólnych w metodach, nawet w klasach, które mają własny typ ogólny. W tym kroku dodasz do pliku Aquarium ogólną metodę, która sprawdza, czy ma on typ WaterSupply.

  1. W klasie Aquarium zadeklaruj metodę hasWaterSupplyOfType(), która przyjmuje parametr ogólny R (T jest już używany) ograniczony do WaterSupply i zwraca true, jeśli waterSupply jest typu R. Jest to funkcja podobna do zadeklarowanej wcześniej, ale w klasie Aquarium.
fun <R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R
  1. Zwróć uwagę, że ostatni znak R jest podkreślony na czerwono. Najedź na nią kursorem, aby zobaczyć, jaki to błąd.
  2. Aby wykonać sprawdzenie is, musisz poinformować język Kotlin, że typ jest reified, czyli rzeczywisty, i może być używany w funkcji. Aby to zrobić, wstaw inline przed fun słowem kluczowym i reified przed typem ogólnym R.
inline fun <reified R: WaterSupply> hasWaterSupplyOfType() = waterSupply is R

Po reifikacji typu możesz go używać jak zwykłego typu, ponieważ po wstawieniu jest on prawdziwym typem. Oznacza to, że możesz wykonywać sprawdzanie typu is.

Jeśli nie użyjesz tu reified, typ nie będzie wystarczająco „rzeczywisty”, aby Kotlin zezwalał na sprawdzanie is. Dzieje się tak, ponieważ typy niekonkretne są dostępne tylko w czasie kompilacji i nie mogą być używane przez program w czasie działania. Więcej informacji znajdziesz w następnej sekcji.

  1. Przekaż TapWater jako typ. Podobnie jak w przypadku wywoływania funkcji ogólnych, wywołuj metody ogólne, używając nawiasów ostrych z typem po nazwie funkcji. Uruchom program i sprawdź wynik.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())   // true
}
⇒ true

Krok 3. Utwórz funkcje rozszerzenia

Typów konkretnych możesz używać też w przypadku zwykłych funkcji i funkcji rozszerzających.

  1. Poza klasą Aquarium zdefiniuj funkcję rozszerzenia w klasie WaterSupply o nazwie isOfType(), która sprawdza, czy przekazany obiekt WaterSupply jest określonego typu, np. TapWater.
inline fun <reified T: WaterSupply> WaterSupply.isOfType() = this is T
  1. Wywołaj funkcję rozszerzenia tak samo jak metodę.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.waterSupply.isOfType<TapWater>())  
}
⇒ true

W przypadku tych funkcji rozszerzenia nie ma znaczenia, jakiego typu jest Aquarium (Aquarium, TowerTank czy inna podklasa), o ile jest to Aquarium. Składnia projekcji gwiazdowej to wygodny sposób na określenie różnych dopasowań. Gdy używasz projekcji gwiazd, Kotlin również dba o to, aby nie wykonywać żadnych niebezpiecznych działań.

  1. Aby użyć projekcji gwiazd, po znaku Aquarium wpisz <*>. Przenieś hasWaterSupplyOfType(), aby była funkcją rozszerzenia, ponieważ nie jest częścią podstawowego interfejsu API Aquarium.
inline fun <reified R: WaterSupply> Aquarium<*>.hasWaterSupplyOfType() = waterSupply is R
  1. Zmień wywołanie na hasWaterSupplyOfType() i uruchom program.
fun genericsExample() {
    val aquarium = Aquarium(TapWater())
    println(aquarium.hasWaterSupplyOfType<TapWater>())
}
⇒ true

W poprzednim przykładzie musieliśmy oznaczyć typ ogólny jako reified, a funkcję jako inline, ponieważ Kotlin musi znać te informacje w czasie działania programu, a nie tylko w czasie kompilacji.

Wszystkie typy ogólne są używane przez Kotlina tylko w czasie kompilacji. Dzięki temu kompilator może mieć pewność, że wszystko robisz bezpiecznie. W czasie działania programu wszystkie typy ogólne są usuwane, stąd wcześniejszy komunikat o błędzie związanym ze sprawdzaniem usuniętego typu.

Okazuje się, że kompilator może utworzyć prawidłowy kod bez przechowywania typów ogólnych do czasu wykonania. Oznacza to jednak, że czasami wykonujesz działania, np. is sprawdzasz typy ogólne, których kompilator nie obsługuje. Dlatego w języku Kotlin dodano typy reified, czyli rzeczywiste.

Więcej informacji o typach konkretnych i wymazywaniu typów znajdziesz w dokumentacji języka Kotlin.

W tej lekcji skupiliśmy się na typach ogólnych, które są ważne, ponieważ sprawiają, że kod jest bardziej elastyczny i łatwiejszy do ponownego wykorzystania.

  • Twórz klasy ogólne, aby zwiększyć elastyczność kodu.
  • Dodaj ogólne ograniczenia, aby ograniczyć typy używane z typami ogólnymi.
  • Używaj typów inout z typami ogólnymi, aby zapewnić lepsze sprawdzanie typów i ograniczyć typy przekazywane do klas lub zwracane z nich.
  • Twórz ogólne funkcje i metody do pracy z typami ogólnymi. Na przykład:
    fun <T: WaterSupply> isWaterClean(aquarium: Aquarium<T>) { ... }
  • Używaj ogólnych funkcji rozszerzających, aby dodać do klasy funkcje inne niż podstawowe.
  • Typy konkretne są czasami niezbędne ze względu na usuwanie typów. Typy konkretne, w przeciwieństwie do typów ogólnych, są zachowywane w czasie działania programu.
  • Użyj funkcji check(), aby sprawdzić, czy kod działa zgodnie z oczekiwaniami. Na przykład:
    check(!waterSupply.needsProcessing) { "water supply needs processing first" }

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.

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 poniższych opcji jest konwencją nazewnictwa typu ogólnego?

▢ <Gen>

▢ <Generic>

▢ <T>

▢ <X>

Pytanie 2

Ograniczenie typów dozwolonych w przypadku typu ogólnego nazywa się:

▢ ogólne ograniczenie,

▢ ogólne ograniczenie,

▢ rozróżnianie

▢ ogólny limit typu,

Pytanie 3

Ukonkretnione oznacza:

▢ Obliczono rzeczywisty wpływ obiektu na wykonanie.

▢ W zajęciach ustawiono indeks z ograniczonym dostępem.

▢ Parametr typu ogólnego został przekształcony w rzeczywisty typ.

▢ Wskaźnik błędu zdalnego został aktywowany.

Przejdź do następnej lekcji: 6. manipulacja funkcjonalna,

Omówienie kursu, w tym linki do innych ćwiczeń, znajdziesz w artykule „Kotlin Bootcamp for Programmers: Welcome to the course” (w języku angielskim).