Stufe 3Objekte und Typen · Lektion 15 von 34
Pattern Matching
Pattern Matching ist eines der Werkzeuge, die Scala-Code so kompakt machen. Ein match-Ausdruck vergleicht einen Wert nicht nur mit Konstanten, sondern prüft Typen, zerlegt Datenstrukturen und bindet deren Teile an Namen – in einem Schritt.
Die Grundform
def beschreibe(x: Any): String = x match
case 0 => "null"
case i: Int if i < 0 => "negative Zahl"
case i: Int => s"positive Zahl $i"
case s: String => s"Text mit ${s.length} Zeichen"
case _ => "etwas anderes"Die Fälle werden von oben nach unten geprüft; der erste passende gewinnt. _ passt auf alles. Findet sich kein passender Fall, wirft Scala zur Laufzeit einen MatchError – ein Grund, bei offenen Typen wie Any immer einen Auffangfall zu haben.
Arten von Mustern
| Muster | Beispiel | passt, wenn … |
|---|---|---|
| Konstante | case 42, case "ja" | der Wert gleich ist |
| Platzhalter | case _ | immer |
| Variable | case x | immer, und bindet den Wert an x |
| Typ | case s: String | der Wert diesen Typ hat |
| Alternative | case "j" | "ja" | eine der Alternativen passt |
| case class | case Punkt(x, 0) | Typ passt und Felder passen |
| Tupel | case (a, b) | ein Paar vorliegt |
| Liste | case kopf :: rest | die Liste nicht leer ist |
| Guard | case x if x > 10 | Muster passt und Bedingung erfüllt ist |
Kleingeschriebene Namen im Muster sind neue Variablen, großgeschriebene werden als Konstanten gelesen. Wollen Sie mit einem kleingeschriebenen, bereits existierenden Wert vergleichen, setzen Sie ihn in Backticks.
case classes zerlegen
Die eigentliche Stärke zeigt sich bei eigenen Datentypen:
enum Form:
case Kreis(radius: Double)
case Rechteck(breite: Double, hoehe: Double)
case Dreieck(a: Double, b: Double, c: Double)
import Form.*
def flaeche(f: Form): Double = f match
case Kreis(r) => math.Pi * r * r
case Rechteck(b, h) => b * h
case Dreieck(a, b, c) =>
val s = (a + b + c) / 2
math.sqrt(s * (s - a) * (s - b) * (s - c))Weil Form ein enum ist (oder ein sealed trait), weiß der Compiler, welche Fälle es gibt. Fehlt einer, meldet er eine Warnung: match may not be exhaustive. Das ist ein großer Vorteil gegenüber einer Kette von if-Abfragen: Kommt später eine neue Form hinzu, zeigt der Compiler jede Stelle, die angepasst werden muss.
Verschachtelte Muster
Muster lassen sich beliebig tief verschachteln:
case class Adresse(ort: String, land: String)
case class Person(name: String, adresse: Option[Adresse])
def herkunft(p: Person): String = p match
case Person(n, Some(Adresse(ort, "CH"))) => s"$n wohnt in der Schweiz, in $ort"
case Person(n, Some(Adresse(_, land))) => s"$n wohnt in $land"
case Person(n, None) => s"Von $n ist keine Adresse bekannt"Listen und Tupel
def zusammenfassen(xs: List[Int]): String = xs match
case Nil => "leer"
case x :: Nil => s"genau ein Element: $x"
case x :: y :: Nil => s"zwei Elemente: $x und $y"
case x :: rest => s"beginnt mit $x, danach ${rest.size} weitere"Der Operator :: zerlegt eine Liste in Kopf und Rest, siehe List. Tupel zerlegt man mit runden Klammern – ideal, um mehrere Werte gleichzeitig zu prüfen:
def spiel(a: String, b: String): String = (a, b) match
case (x, y) if x == y => "unentschieden"
case ("Stein", "Schere") | ("Schere", "Papier") | ("Papier", "Stein") => "Spieler A gewinnt"
case _ => "Spieler B gewinnt"Variablen binden mit @
Manchmal wollen Sie ein Muster prüfen und den ganzen Wert behalten. Dafür gibt es @:
def pruefe(f: Form): String = f match
case k @ Kreis(r) if r > 100 => s"großer Kreis: $k"
case andere => s"normal: $andere"Pattern Matching außerhalb von match
Muster tauchen an vielen Stellen auf – überall dort, wo Werte gebunden werden:
val (x, y) = (3, 4) // Zerlegen bei der Definition
val Punkt = raw"(\d+),(\d+)".r
val Punkt(px, py) = "10,20": @unchecked // regulärer Ausdruck als Muster
for (name, alter) <- List(("Ada", 36), ("Alan", 41)) do println(name)
List(Some(1), None, Some(3)).collect { case Some(n) => n * 10 } // List(10, 30)collect nimmt eine partielle Funktion – einen Block aus case-Fällen – und behält nur die Elemente, auf die ein Fall passt. Die Angabe @unchecked sagt dem Compiler, dass Sie wissen, dass das Muster scheitern könnte.
Muster statt instanceof
Wer aus Java kommt, kennt Ketten wie if (x instanceof A) { A a = (A) x; … }. In Scala ersetzt das Typ-Muster diese Konstruktion vollständig. Explizite Umwandlungen mit asInstanceOf braucht man in normalem Scala-Code praktisch nie.