1. Treści programowe:
Podstawowe elementy języka Kotlin: deklaracja i wywoływanie
funkcji, funkcje lambda
2. Cel zajęć:
Celem zajęć jest zrozumienie zasad podstawowych
elementów języka Kotlin w porównaniu ze znanymi już takimi jak Java czy C++.
3. Materiały dydaktyczne
I. Deklaracja funkcji
Funkcje w Kotlinie są
niezwykle elastyczne. Można je deklarować na wiele sposobów: od tradycyjnych
bloków, przez funkcje jednowierszowe, aż po zaawansowane funkcje anonimowe i
lambda.
1.
Funkcje nie zwracające wartości (Unit) oraz nie
pobierająca żadnych argumentów
Jeśli funkcja nic nie zwraca
(odpowiednik void w Javie), jej typem zwracanym jest
Unit. Możesz go dopisać jawnie lub pominąć.
fun drukujInformacje(): Unit {
println("Kotlin
jest fantastyczny")
}
// Wersja uproszczona (najczęściej stosowana)
fun drukujInformacjeProsto(){
println("Kotlin
jest fantastyczny")
}
2. Funkcje
pobierająca wartości jako argumenty oraz mechanizm przeciążania
Przykład funkcji
pobierającej wartości oraz funkcje przeciążone. Mechanizm znany z innych
języków programowania np. Javy.
//funkcja pobierająca wartość String jako argument
fun pokazMojTekst(text: String){
for(i
in 1..1)
println("To
jest mój tekst: $text")
}
//funkcja przeciążona przez ilość argumentów (String oraz Int)
fun pokazMojTekst(text: String, licznik: Int){
for(i
in 1..licznik)
println("To
jest mój tekst: $text")
}
fun main(){
pokazMojTekst("Pojedynczy
tekst") //wywołanie z jednym argumentem
pokazMojTekst("Kolejna
linia tekstu", 3)
//wywołanie z dwoma argumentami
}
3. Funkcje
zwracająca wartość
To najbardziej klasyczny
sposób, przypominający inne języki, ale z typem zwracanym na końcu.
fun powitanie(imie: String): String {
return
"Witaj, $imie!"
}
4. Funkcje jednowyrażeniowe (Single-Expression)
Jeśli funkcja zawiera tylko
jedno wyrażenie, możesz pominąć klamry {} oraz słowo return. Kompilator sam
domyśli się typu zwracanego.
fun pomnoz(a:
Int, b: Int) = a * b
fun getResult(a: Int,
b: Int) = if (a
!= b) 0 else 1
5. Parametry domyślne i argumenty nazwane
Kotlin pozwala definiować
wartości domyślne, co eliminuje potrzebę przeciążania funkcji (overloading).
fun stworzProfil(imie:
String="Brak", status: String = "Aktywny",
punkty: Int = 0)
{
println("$imie ($status)
- $punkty pkt")
}
fun main()
{
stworzProfil("Adam")
// Użyje domyślnych: "Aktywny", 0
stworzProfil("Ewa",
punkty = 100) //
Argument nazwany - pomijamy 'status'
stworzProfil(punkty
= 220) //Wskazanie
nazwy zmiennej punkty z pominęciem pozostałych
stworzProfil(punkty
= 220, status= "Nieaktywny") //Wskazanie zmiennych w innej
kolejności niż w deklaracji
}
6. Funkcje z różną liczbą argumentów (vararg)
Jeśli nie wiesz, ile
argumentów przekaże użytkownik, użyj vararg.
fun sumaWszystkich(vararg liczby:
Int): Int {
return
liczby.sum()
}
// Wywołanie: sumaWszystkich(1, 2, 5, 10)
7. Funkcje rozszerzające (Extension Functions)
To jedna z najpotężniejszych
cech Kotlina. Pozwala "dodać" nową funkcję do istniejącej klasy bez
jej modyfikowania.
// Dodajemy metodę 'czyParzysta' do klasy Int
fun Int.czyParzysta(): Boolean {
return
this % 2 == 0
}
val liczba
= 10
println(liczba.czyParzysta()) // true
8. Funkcje Lambda i Anonimowe
Lambda to anonimowa funkcja, czyli funkcja bez nazwy,
którą możesz przypisać do zmiennej lub przekazać jako argument do innej
funkcji.
W Kotlinie wygląda tak:
{ parametry -> ciało }
· {} – oznacza lambdę
· parametry – lista parametrów (może
być pusta)
· -> – separator między
parametrami a ciałem
· ciało - instrukcje, które wykona
lambda
val greet = { name: String
-> println("Cześć,
$name!")
}
greet("Adam") // wypisze: Cześć, Adam!
· greet jest zmienną typu lambda (String) -> Unit
· przekazujesz parametr name
· wykonujesz kod println(...)
- Lambda bez parametrów:
val sayHello = { println("Hello world!") }
sayHello() // wypisze: Hello world!
· Brak parametrów - puste {}
· Można wywołać jak normalną funkcję
- Skróty w lambdach
Jeśli lambda ma jeden
parametr, możesz użyć it:
val result2
= operateOnNumber(7)
{ it + 3 } // it
= parametr
println(result2) // wypisze: 10
it automatycznie wskazuje
pierwszy parametr
val numbers = listOf(1,
2, 3)
numbers.forEach {
println(it)
} // 'it' zastępuje każdy element listy
-
Lambdy z wieloma parametrami
val sum:
(Int, Int) -> Int = { a, b -> a + b }
println(sum(3,
4)) //
7
· Typ (Int, Int) -> Int oznacza funkcję
przyjmującą dwa Inty i zwracającą Int
· Lambda { a, b -> a + b } implementuje ten typ
9. Funkcje wyższego rzędu (Higher-Order
Functions)
Funkcja, która przyjmuje
inną funkcję jako parametr lub ją zwraca.
fun wykonajOperacje(a:
Int, b: Int, operacja: (Int, Int) -> Int): Int {
return
operacja(a, b)
}
val wynik
= wykonajOperacje(5, 5) {
x, y -> x + y } //
wynik = 10
Najczęściej lambdy używa
się, żeby przekazać kod do wykonania:
fun operateOnNumber(x:
Int, operation: (Int) -> Int): Int {
return operation(x)
}
// użycie
val result = operateOnNumber(5)
{ number -> number
* 2 }
println(result) // wypisze: 10
· operation: (Int) -> Int - typ funkcji przyjmującej Int
i zwracającej Int
· { number -> number * 2 } - lambda jako argument
· Funkcja operateOnNumber
wywołuje ją z x = 5
|
Typ funkcji |
Charakterystyka |
|
Standardowa |
Pełna kontrola, wiele linii kodu. |
|
Jednowierszowa |
Krótka, zwięzła, bez return. |
|
Z Unit |
Nie zwraca danych (tylko efekt
uboczny, np. druk). |
|
Z parametrami domyślnymi |
Redukuje liczbę potrzebnych funkcji. |
|
Rozszerzająca |
"Dopisuje" logikę do
gotowych klas (np. String, Int). |
|
Lambda |
Funkcja w formie "skrótu
myślowego", idealna do list (np. filter,
map). |
9. Jawna deklaracja typu funkcyjnego
Jawna deklaracja typu funkcyjnego to sytuacja, w
której nie pozwalamy Kotlinowi „zgadywać”, czym jest dana zmienna, ale sami
precyzyjnie opisujemy jej „sygnaturę” (czyli co przyjmuje i co zwraca).
Najlepiej zrozumieć to na przykładzie systemu
walidacji hasła.
Przykład: Walidator Hasła
Wyobraź sobie, że chcesz stworzyć zmienną, która
przechowuje regułę sprawdzania hasła. Każda reguła przyjmuje String i zwraca Boolean (czy hasło jest poprawne).
// 1. Jawna deklaracja typu: (String) -> Boolean
val isPasswordLongEnough:
(String) -> Boolean = { password
->
password.length >=
8
}
val hasAdminPrivileges:
(Int) -> Boolean = { id
->
id == 1
}
Gdy widzisz dwukropek w deklaracji typu funkcyjnego,
czytaj go w ten sposób:
Użycie:
fun main() {
// Użycie isPasswordLongEnough
val pass1
= "123"
val pass2
= "tajneHaslo123"
println(isPasswordLongEnough(pass1)) // Wypisze: false
println(isPasswordLongEnough(pass2)) // Wypisze: true
// Użycie hasAdminPrivileges
val userId = 1
if (hasAdminPrivileges(userId)) {
println("Witaj
Administratorze!")
} else {
println("Brak
uprawnień.")
}
}
10. Różnica między jawną deklaracją typu funkcyjnego a
zwykłą lambdą
· Zwykła lambda (Inferencja
typów)
W tym przypadku pozwalasz kompilatorowi Kotlina, aby
sam wywnioskował typy na podstawie tego, co wpisałeś wewnątrz klamerek { }.
// Kotlin sam zgaduje: "Aha, nazwa to String,
a wynik to Boolean"
val isLong =
{ name: String -> name.length >
5 }
Zaleta: Piszesz mniej kodu. Jest to czytelne przy krótkich
lambdach.
Wada: Jeśli lambda jest długa i skomplikowana, kompilator
może mieć problem z "odgadnięciem" typu, albo Ty jako programista
stracisz jasny podgląd na to, co ta funkcja ma zwracać.
· Jawna deklaracja typu
funkcyjnego
Tutaj narzucasz typ przed znakiem równości.
Mówisz kompilatorowi: "Zmienna validator ma być
typu (String) -> Boolean i nie interesuje mnie,
jak to sprawdzisz, ale musisz się tego trzymać".
// Jawny kontrakt: (String) -> Boolean
val validator: (String) -> Boolean = { it.length >
5 }
Zaleta: Masz pełną kontrolę. Jeśli wewnątrz klamerek
napiszesz coś, co zwraca Int zamiast Boolean, kompilator natychmiast wyrzuci błąd.
Wartość: Dzięki temu, że typ jest już znany (po lewej
stronie), wewnątrz lambdy możesz użyć magicznego słowa it
(zamiast name: String), co jeszcze bardziej skraca
kod.
11. Zwracanie lambdy przez zwykłą funkcję
fun getPasswordValidator(): (String) -> Boolean {
return {
password -> password.length >=
8 }
}
fun getAdminChecker():
(Int) -> Boolean {
return {
id -> id == 1 }
}
// Użycie:
val myValidator =
getPasswordValidator()
println(myValidator("haslo123"))
Zadania
Zadanie 1:
Podstawy deklaracji i typy zwracane
Zaimplementuj system obliczeń geometrycznych. Stwórz
następujące funkcje:
Zadanie 2:
Przeciążanie funkcji (Overloading)
Stwórz zestaw funkcji o nazwie formatData,
które będą obsługiwać różne typy wejściowe:
Zadanie 3:
Parametry domyślne i argumenty nazwane
Zaprojektuj funkcję generateProfile,
która tworzy opis użytkownika:
Zadanie 4:
Funkcje jednowyrażeniowe (Single-expression functions)
Zapisz poniższe operacje w formie skróconej (z
użyciem znaku =):
Zadanie 5:
Zmienna liczba argumentów (vararg)
Napisz funkcje statystyczne:
Zadanie 6:
Funkcje rozszerzające (Extension functions)
Dodaj nową funkcjonalność do istniejących typów bez
dziedziczenia:
Zadanie 7:
Podstawowe wyrażenia lambda
Zdefiniuj i wywołaj następujące lambdy:
Lambda
bezparametrowa (systemReport):
Lambda z jednym parametrem (displayGreeting):
Lambda z wieloma parametrami (errorLogger):
Zadanie 8:
Skróty w lambdach i słowo kluczowe it
Napisz funkcję wyższego rzędu modifyString(text: String, transformation:
(String) -> String): String. Następnie użyj jej w main()
do:
Zadanie 9:
Zaawansowane typy funkcyjne z wieloma parametrami
Zadeklaruj zmienne przechowujące typy funkcyjne i
przypisz do nich odpowiednie lambdy: