Stufe 3Objekte und Typen · Lektion 13 von 34
Klassen und Objekte
Scala ist durch und durch objektorientiert: Jeder Wert ist ein Objekt, jede Operation ein Methodenaufruf. Klassen sind dabei knapper als in Java – ein Konstruktor passt oft in eine Zeile, und case classes erledigen die Routinearbeit von allein.
Eine erste Klasse
class Konto(val inhaber: String, private var saldo: BigDecimal):
def einzahlen(betrag: BigDecimal): Unit =
require(betrag > 0, "Betrag muss positiv sein")
saldo += betrag
def kontostand: BigDecimal = saldo
val k = Konto("Ada", 100)
k.einzahlen(50)
k.kontostand // 150
k.inhaber // "Ada"Die Parameterliste hinter dem Klassennamen ist der primäre Konstruktor. Steht val davor, wird der Parameter zu einem öffentlichen, unveränderlichen Feld; var macht ihn veränderlich; private versteckt ihn. Ohne val/var ist der Parameter nur innerhalb der Klasse sichtbar.
Das Schlüsselwort new ist in Scala 3 nicht mehr nötig: Konto("Ada", 100) genügt. require prüft eine Vorbedingung und wirft bei Verletzung eine IllegalArgumentException.
Konstruktoren und Anfangscode
Alles, was im Rumpf der Klasse steht und keine Definition ist, wird beim Erzeugen ausgeführt. Weitere Konstruktoren heißen this:
class Punkt(val x: Double, val y: Double):
println(s"Punkt ($x, $y) erzeugt") // läuft bei jedem Erzeugen
def this(x: Double) = this(x, 0.0) // Hilfskonstruktor
def abstand: Double = math.hypot(x, y)In der Praxis sind Hilfskonstruktoren selten, weil Standardwerte (y: Double = 0.0) meist eleganter sind.
Sichtbarkeit
Ohne Angabe ist alles öffentlich. private beschränkt den Zugriff auf die Klasse (und ihr Begleitobjekt), protected auf die Klasse und ihre Unterklassen. Feiner lässt es sich mit private[paketname] steuern: sichtbar im genannten Paket, sonst nicht.
object: Singletons
Ein object ist eine Klasse mit genau einer Instanz, die beim ersten Zugriff erzeugt wird:
object Zaehler:
private var stand = 0
def naechster(): Int =
stand += 1
stand
Zaehler.naechster() // 1
Zaehler.naechster() // 2Scala hat kein static. Was in Java statisch wäre, gehört in ein object.
Begleitobjekte und apply
Heißen eine Klasse und ein Objekt gleich und stehen sie in derselben Datei, ist das Objekt ein Begleitobjekt (companion object). Beide dürfen gegenseitig auf ihre privaten Mitglieder zugreifen. Typisch sind Fabrikmethoden:
class Temperatur private (val kelvin: Double)
object Temperatur:
def apply(celsius: Double): Temperatur = new Temperatur(celsius + 273.15)
def ausFahrenheit(f: Double): Temperatur = apply((f - 32) * 5 / 9)
val t = Temperatur(21.5) // ruft Temperatur.apply(21.5) aufDie Methode apply ist besonders: x(…) ist eine Abkürzung für x.apply(…). Deshalb funktioniert auch liste(0) für den Zugriff auf ein Listenelement.
case classes
Für Klassen, die vor allem Daten tragen, gibt es case class. Sie erhalten automatisch:
- öffentliche, unveränderliche Felder für alle Parameter,
equalsundhashCode, die die Inhalte vergleichen,- eine lesbare
toString-Ausgabe, - eine
copy-Methode und - Unterstützung für Pattern Matching.
case class Adresse(strasse: String, ort: String)
val a = Adresse("Hauptstraße 1", "Lausanne")
val b = a.copy(ort = "Genf")
a == Adresse("Hauptstraße 1", "Lausanne") // true
println(b) // Adresse(Hauptstraße 1,Genf)Mit copy ändern Sie keine Objekte, sondern erzeugen eine Kopie mit einzelnen neuen Werten – der übliche Weg, mit unveränderlichen Daten zu arbeiten.
Vererbung
abstract class Form:
def flaeche: Double
override def toString = f"${getClass.getSimpleName} mit Fläche $flaeche%.2f"
class Kreis(r: Double) extends Form:
def flaeche = math.Pi * r * r
class Rechteck(b: Double, h: Double) extends Form:
def flaeche = b * hEine Klasse erbt von höchstens einer Klasse, kann aber beliebig viele Traits einmischen. override ist Pflicht, wenn eine bereits implementierte Methode überschrieben wird. Mit final verhindern Sie weitere Vererbung. Scala 3 kennt zudem das Schlüsselwort open: Es kennzeichnet Klassen, die ausdrücklich zum Erweitern gedacht sind. In strengeren Compiler-Einstellungen gibt es eine Warnung, wenn außerhalb der Datei von einer Klasse ohne open geerbt wird – Vererbung soll eine bewusste Entscheidung sein.
enum
Für Aufzählungen gibt es in Scala 3 enum:
enum Ampel:
case Rot, Gelb, Gruen
def darfFahren: Boolean = this == Gruen
Ampel.values // Array(Rot, Gelb, Gruen)
Ampel.valueOf("Gelb") // GelbFälle eines enums dürfen auch Parameter haben – dazu mehr beim Pattern Matching.
Wo alles hängt: Any, AnyVal, AnyRef
Any ist die Wurzel aller Typen und bietet Methoden wie ==, hashCode und toString. Darunter liegen AnyVal für Werttypen wie Int und Boolean sowie AnyRef (entspricht java.lang.Object) für alle anderen. Ganz unten steht Nothing, der Typ von Ausdrücken, die nie einen Wert liefern – etwa throw.