Funktionen & Lambdas
Funktionen in Kotlin: Default-Parameter, Named Arguments, Extension Functions und das beliebte Lambda mit it und Trailing Closures.
Inhaltsverzeichnis
Funktionen & Lambdas
Kotlin verbindet Objektorientierung mit funktionalen Konzepten - und das fuehlt sich elegant an. Lambdas sind im Alltag allgegenwaertig.
Grundform
fun gruessen(name: String) {
println("Hallo, $name!")
}
gruessen("Anna")
Rueckgabewert
fun addieren(a: Int, b: Int): Int {
return a + b
}
Ein-Ausdruck-Funktionen
Wenn der Funktionsrumpf nur ein Ausdruck ist:
fun addieren(a: Int, b: Int): Int = a + b
// Rueckgabetyp kann der Compiler ableiten:
fun addieren2(a: Int, b: Int) = a + b
Kurz und knackig.
Default-Werte
fun gruessen(name: String = "Welt", gruss: String = "Hallo") {
println("$gruss, $name!")
}
gruessen() // "Hallo, Welt!"
gruessen("Anna") // "Hallo, Anna!"
gruessen("Leo", "Hi") // "Hi, Leo!"
Named Arguments
gruessen(gruss = "Moin", name = "Tim")
Besonders hilfreich, wenn du viele Parameter hast - oder nur einen aus der Mitte setzen willst:
gruessen(gruss = "Hi") // name bleibt bei "Welt"
vararg - Variadic
fun summe(vararg zahlen: Int): Int {
return zahlen.sum()
}
summe(1, 2, 3) // 6
summe(1, 2, 3, 4, 5) // 15
val liste = intArrayOf(10, 20, 30)
summe(*liste) // 60 - Spread-Operator
Funktionen als Werte
Funktionen sind First-Class Citizens:
fun verdoppeln(n: Int): Int = n * 2
fun main() {
val f: (Int) -> Int = ::verdoppeln
println(f(5)) // 10
}
::verdoppeln ist eine Function Reference.
Higher-Order Functions
Funktionen, die andere Funktionen nehmen oder zurueckgeben:
fun anwenden(f: (Int) -> Int, wert: Int): Int {
return f(wert)
}
fun main() {
println(anwenden(::verdoppeln, 5)) // 10
println(anwenden({ it * it }, 5)) // 25
}
Lambdas
Anonyme Funktionen - kompakt und sehr haeufig genutzt:
val verdoppeln: (Int) -> Int = { n -> n * 2 }
println(verdoppeln(5)) // 10
Bei einem Parameter: it
val verdoppeln: (Int) -> Int = { it * 2 }
it ist ein impliziter Name fuer das einzige Argument.
Trailing Lambda
Wenn eine Funktion als letzten Parameter eine Lambda nimmt, kann sie ausserhalb der Klammern stehen:
fun anwenden(wert: Int, f: (Int) -> Int): Int = f(wert)
val ergebnis = anwenden(5) { it * 2 } // kein Komma, Lambda ausserhalb
Das ist ein sehr gelaeufiges Muster in Kotlin - viele APIs sind so aufgebaut.
Collection-Operationen mit Lambdas
Das ist der Ort, wo Lambdas wirklich glaenzen:
val zahlen = listOf(1, 2, 3, 4, 5)
zahlen.map { it * 2 } // [2, 4, 6, 8, 10]
zahlen.filter { it % 2 == 0 } // [2, 4]
zahlen.reduce { a, b -> a + b } // 15
zahlen.sumOf { it } // 15
zahlen.maxOrNull() // 5
zahlen.any { it > 3 } // true
zahlen.all { it > 0 } // true
zahlen.find { it > 3 } // 4
Ketten
val ergebnis = zahlen
.filter { it > 1 }
.map { it * 10 }
.sumOf { it }
println(ergebnis) // 140
Extension Functions
Hier wirdโs magisch - du kannst Funktionen an existierende Typen anhaengen:
fun String.tage(): List<String> = this.split(",").map { it.trim() }
fun main() {
val wochentage = "Mo, Di, Mi, Do, Fr".tage()
println(wochentage) // [Mo, Di, Mi, Do, Fr]
}
this bezieht sich auf den String, auf dem die Funktion aufgerufen wird. Das ist keine Monkey-Patching-Magie, sondern typsicher - der Compiler prueft alles.
Ein haeufiges Beispiel
fun Int.istGerade(): Boolean = this % 2 == 0
val x = 4
println(x.istGerade()) // true
println(7.istGerade()) // false
Warum Extension Functions?
- API-Design ohne Vererbung
- Lesbarer Code (
"Hallo".tage()statttage("Hallo")) - Viele Kotlin-Standard-Methoden sind so implementiert
Lokale Funktionen
Funktionen innerhalb von Funktionen - nuetzlich, wenn eine Hilfsfunktion nur lokal Sinn macht:
fun verarbeite(text: String): List<String> {
fun saeubern(s: String): String = s.trim().lowercase()
return text.split(",")
.map { saeubern(it) }
.filter { it.isNotBlank() }
}
Benannte vs. anonyme Funktionen
Manchmal willst du eine anonyme Funktion mit explizitem Rueckgabetyp:
val addiere = fun(a: Int, b: Int): Int {
return a + b
}
Selten gebraucht - Lambdas reichen fast immer.
let, apply, run, with, also - die Scope Functions
Kotlin hat fuenf kleine Helfer, die oft auftauchen:
let - mit dem Wert arbeiten
val name: String? = "Anna"
name?.let {
println("Hallo, $it!")
}
apply - Objekt konfigurieren
val p = Person().apply {
name = "Anna"
alter = 28
}
Das Objekt selbst wird zurueckgegeben.
run - Block ausfuehren, Ergebnis zurueck
val laenge = "Hallo".run { this.length }
with - wie run, aber mit Argument
val text = with(person) {
"Name: $name, Alter: $alter"
}
also - Seiteneffekt, Objekt zurueck
val x = berechneWert().also {
log("Wert: $it")
}
Im Alltag sind let und apply die wichtigsten.
Praktisches Beispiel
data class Produkt(val name: String, val preis: Double, val lager: Int)
fun List<Produkt>.verfuegbar(): List<Produkt> =
this.filter { it.lager > 0 }
fun List<Produkt>.gesamtwert(): Double =
this.sumOf { it.preis * it.lager }
fun main() {
val sortiment = listOf(
Produkt("Buch", 19.99, 5),
Produkt("Kaffee", 3.50, 0),
Produkt("Tasse", 8.00, 10),
)
println(sortiment.verfuegbar().map { it.name }) // [Buch, Tasse]
println(sortiment.gesamtwert()) // 179.95
}
Extension Functions plus funktionaler Stil - sehr idiomatisches Kotlin.
Zusammenfassung
- Funktionen mit
fun, Ein-Ausdruck-Variante mit= - Default-Werte und Named Arguments machen Aufrufe lesbar
varargfuer beliebig viele Argumente- Lambdas sind kompakt (
{ it * 2 }) - Trailing Lambda ist Standard - Extension Functions erweitern Typen ohne Vererbung
- Scope Functions (
let,apply,run,with,also) fuer elegantes Chaining
Im naechsten Kapitel: Klassen, Data Classes und Objects - objektorientiert auf Kotlin-Art.