scalatutorial.de

Stufe 3Objekte und Typen · Lektion 13 von 34

Klassen und Objekte

Aktualisiert am 24. September 20263 Min. LesezeitSchritt 1/6 dieser Stufe

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

 Scala 3
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:

 Scala 3
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:

 Scala 3
object Zaehler:
  private var stand = 0
  def naechster(): Int =
    stand += 1
    stand

Zaehler.naechster()   // 1
Zaehler.naechster()   // 2

Scala 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:

 Scala 3
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) auf

Die 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:

 Scala 3
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

 Scala 3
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 * h

Eine 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:

 Scala 3
enum Ampel:
  case Rot, Gelb, Gruen

  def darfFahren: Boolean = this == Gruen

Ampel.values            // Array(Rot, Gelb, Gruen)
Ampel.valueOf("Gelb")   // Gelb

Fälle eines enums dürfen auch Parameter haben – dazu mehr beim Pattern Matching.

Wo alles hängt: Any, AnyVal, AnyRef

Die Typhierarchie von Scala: Any, AnyVal, AnyRef, Null und NothingAnyAnyValAnyRefIntDoubleBooleanCharUnitStringList[A]eigene KlassenNullNothingNothing ist Untertyp von allem,z. B. der Typ von throw.
Jeder Typ in Scala ist ein Untertyp von Any.

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.

Passt dazu