Primärkonstruktor (engl. primary constructor)

Jede Klasse hat genau einen Primärkonstruktor, der zur Konstruktion des Objektes aufgerufen werden muss. Die Parameter des Primärkonstruktors werden direkt nach dem Klassennamen in runden Klammern angegeben. Diese Parameter werden auch als Klassenparameter bezeichnet. Auf die Klassenparameter kann innerhalb der gesamten Klasse zugegriffen werden.

class MySimpleClass(paramA: Int, paramB: Double) {
  // Inhalt des Primaerkonstruktors
  val myParam = paramA * paramB
}
itmapa.de - X2H V 0.17

Der Rumpf einer Klasse stellt den Inhalt des Primärkonstruktors dar. Dieser wird dementsprechend bei der Konstruktion einer Klasse aufgerufen.

Hilfskonstruktor (engl. auxiliary constructor)

Neben dem Primärkonstruktor stehen in Scala Hilfskonstruktoren (eng. auxiliary constructor) als Einstiegspunkt zur Objekterzeugung in Scala zur Verfügung.

Ein Hilfskonstruktor (engl. auxiliary constructor) muss im ersten Schritt immer einen weiteren Konstruktor aufrufen. Dies kann ein weiterer Hilfskonstruktor oder der Primärkonstruktor sein. Zu beachten ist hier, dass wenn ein weiterer Hilfskonstruktor aufgerufen werden soll, dass dieser im Quelltext vor dem aufrufenden Konstruktor definiert werden muss. Diese Vorgehensweise führt auch dazu, dass der Primärkonstruktor immer aufgerufen werden muss.

Die Definition eines Hilfskonstruktors entspricht der Definition einer Methode. Anstelle eines frei wählbaren Methodennamens muss an dessen Stelle das Schlüsselwort this verwendet werden. Das nachfolgende Beispiel zeigt die Verwendung eines Hilfskonstruktors.

object Test{
  def main(args: Array[String]){
    val v1 = new MyAuxiliaryConstructor(3,2)
    val v2 = new MyAuxiliaryConstructor(4)
  }
}

class MyAuxiliaryConstructor(value1: Int, value2: Int) {
  println(value1+" "+value2)

  def this(value1: Int) = {
    this(value1,1)
  }
}
itmapa.de - X2H V 0.17

Im vorangegangenen Beispiel wird in der main -Methode zunächst ein Objekt v1 vom Typ MyAuxiliaryConstructor über den Primärkonstruktor erzeugt. Im Anschluss wird ein Objekt v2 über den Hilfskonstruktor der Klasse erzeugt. Die erste Anweisung im Hilfskonstruktor ist der Aufruf des Primärkonstruktor der Klasse MyAuxiliaryConstructor .

Der Ablauf des Beispieles führt zu folgender Ausgabe auf der Systemausgabe:

3 2
4 1
itmapa.de - X2H V 0.17

Wie bereits erwähnt, muss ein Scala Hilfskonstruktor nicht unbedingt den Primärkonstruktor aufrufen, sondern kann einen anderen Hilfskonstruktor aufrufen. Am Ende einer solchen Konstruktorenverkettung muss allerdings der Primärkonstruktor aufgerufen werden.

Das nachfolgende Beispiel ConstructorChain zeigt beispielhaft eine Konstruktorenverkettung.

class ConstructorChain(value1: Int, value2: String, value3: Double) {

  println(value1+" "+value2+" "+value3)
  
  def this (value1: String, value2: Double) = this(1,value1,value2)
  
  def this (value: String) = this(value,2.)
  
  def this () = this("Test")
}

object Starter {
  def main(args: Array[String]) {
    new ConstructorChain()
    new ConstructorChain("Hallo")
    new ConstructorChain("Scala rockt",3.)
    new ConstructorChain(4,"Dies ist ein Test",7)
  }
}
itmapa.de - X2H V 0.17

Der Ablauf dieses Programmes führt zu folgender Ausgabe:

1 Test 2.0
1 Hallo 2.0
1 Scala rockt 3.0
4 Dies ist ein Test 7.0
itmapa.de - X2H V 0.17

Basisklassen Konstruktor

Hat eine zu erweiternde Klasse zumindest einen nicht-leeren Konstruktor, muss die erweiternde Klasse einen Konstruktor der Basisklasse aufrufen. Dies geschieht, indem man nach dem extends Schlüsselwort und dem Namen der zu erweiternden Klasse die Parameter in runden Klammern angibt. Das nachfolgende Beispiel zeigt diese Praxis, wo die Klasse B den Primärkonstruktor der Basisklasse A aufruft.

class A (value1: Int, value2: Double){
  def foo = value1 * value2
}

class B extends A(3,4.5){  
  println(foo)
}
itmapa.de - X2H V 0.17

Die erweiternde Klasse muss nicht den Primärkonstruktor der Basisklasse aufrufen, sondern kann auch jeden definierten Hilfskonstruktor aufrufen. Das nachfolgende Beispiel zeigt diese Praxis.

class C (value1: Int, value2: Double){
  def this(value3: Int) = this(value3,4.6)
  
  def foo = value1 * value2
}

class D extends C(5){  
  println(foo)
}
itmapa.de - X2H V 0.17

Konstruktor Argumente

Auf Konstruktor Argumente kann innerhalb der gesamten Klasse zugegriffen werden. Werden im Konstruktor nur Name und Typ des Arguments angegeben, kann auf die Argumente von außerhalb der Klasse nicht zugegriffen werden. Weiterhin ist eine Veränderung der Argumente auch innerhalb der Klasse nicht möglich.

Das nachfolgende Beispiel zeigt eine Klasse, wo auf die Konstruktor Argumente von außen nicht zugegriffen werden kann.

class ConstructorArguments(a: Int, b: Double){
  def foo: Double = a * b
}
itmapa.de - X2H V 0.17

Der Scala Compiler gibt uns Möglichkeiten, für die Konstruktor Argumente automatisch "Getter" und "Setter" Methoden zu erzeugen. Setzen wir vor einem Konstruktor Argument das Schlüsselwort val erzeugt der Compiler, für das Konstruktor Argument, automatisch eine "Getter"-Methode mit dem Namen des Konstruktor Arguments. Wird vor dem Konstruktor Argument das Schlüsselwort var angegeben, wird das Argument veränderbar und erhält eine Standard "Setter"-Methode.

Sehen wir uns nun folgende einfache Scala Klasse an:

class ConstructorTest(valueA: Int, valueB: Double, valueC: String)
itmapa.de - X2H V 0.17

Disassemblieren wir die Klasse mit javap sehen wir, das wir eine einfache Klasse mit einem Konstruktor erhalten. Die Klasse enthält weder Setter noch Getter - Methoden.

Compiled from "ConstructorTest.scala"
public class ConstructorTest extends java.lang.Object implements scala.ScalaObject{
    public ConstructorTest(int, double, java.lang.String);
}
itmapa.de - X2H V 0.17

Setzen wir nun vor der Variablen valueA das Schlüsselwort val und vor der Variablen valueB das Schlüsselwort var an:

class ConstructorTest(val valueA: Int, var valueB: Double, valueC: String)
itmapa.de - X2H V 0.17

Kompilieren wir die Klasse erneut und disassemblieren Sie mit javap sehen wir, dass die Variable valueA eine Getter -Methode erhalten hat. Die Variable valueB hat sowohl eine Getter als auch eine Setter - Methode erhalten. Die Variable valueC hat keine Getter und keine Setter -Methode.

Compiled from "ConstructorTest.scala"
public class ConstructorTest extends java.lang.Object implements scala.ScalaObject{
    public int valueA();
    public double valueB();
    public void valueB_$eq(double);
    public ConstructorTest(int, double, java.lang.String);
}
itmapa.de - X2H V 0.17

Das nachfolgende Beispiel zeigt den fehlerhaften Versuch, auf einfache Klassenparameter als Feld zuzugreifen die nicht mit val oder var gekennzeichnet sind.

object MainClass {
  def main(args: Array[String]) {
    val aClass = new AClass(1,"123")
    println(aClass.value1)       // Fehler
    println(aClass.value2)       // Fehler
    aClass.value2 = "abc"        // Fehler
  }
}

class AClass(value1: Int, value2: String)
itmapa.de - X2H V 0.18

Eine Möglichkeit das Problem zu lösen wäre die Bereitstellung von entsprechenden Settern und Gettern. Da Variable und Methoden (Funktionen) sich den gleichen Namensraum teilen, müssten wir zusätzliche Bezeichner einführen. Innerhalb der Klasse hätten wir dann zwei Namen für das gleiche Element.

Durch voranstellen von val bzw. var erreichen wir, dass die entsprechenden Zugriffsmethoden erzeugt werden.

Im nachfolgenden Beispiel verwenden wir diese Vorgehensweise im Konstruktor von AClass , sodass der Compiler das Programm fehlerfrei übersetzt.

object MainClass {
  def main(args: Array[String]) {
    val aClass = new AClass(1,"123")
    println(aClass.value1)       // OK
    println(aClass.value2)       // OK
    aClass.value2 = "abc"        // OK
  }
}

class AClass(val value1: Int, var value2: String)
itmapa.de - X2H V 0.17

Benannte Argumente (engl. named arguments)

Wie bei Funktionen können wir in Konstruktoren benannte Argumente verwenden. Auch eine Mischung aus benannten Argumenten und Argumenten, die über Ihre Reihenfolge beim Aufruf definiert werden, ist möglich. Wurde jedoch einmal ein Argument über den Namen bestimmt, können die weiteren Argumente nur noch über Ihren Namen belegt werden.

object MainClass{
  def main(args: Array[String]) {
    new A(1, 2, v4=3, v3=4)
  }
}

class A(v1: Int, v2: Int, v3: Int, v4: Int) {
  println(v1+" "+v2+" "+v3+" "+v4)
}
itmapa.de - X2H V 0.17

Die Ausführung des Programms führt zu folgender Ausgabe auf der Systemausgabe:

1 2 4 3 
itmapa.de - X2H V 0.17

Standardargumente (engl. default arguments)

Wie bei Funktionen können wir in Konstruktoren Standardargumente definieren. Wird ein Argument bei einem Konstruktorenaufruf nicht übergeben, so wird der Wert auf den Standard gesetzt, welcher in der Argumentenliste definiert wird. Standardargumente eignen sich hervorragend Hilfskonstruktoren zu vermeiden deren Hauptaufgabe ist Standardwerte an den Primärkonstruktor übergeben werden. Im nachfolgenden Beispiel werden die Konstruktorargumente value1 und value2 als Standardargumente definiert.

object MainObject {
  def main(args: Array[String]) {
    new StandardArgument(1,2,3)
    new StandardArgument(1,2)
    new StandardArgument(1)
  }
}

class StandardArgument(value1: Int, value2: Int = 4, value3: Int = 5) {
  println(value1+" "+value2+" "+value3)
}
itmapa.de - X2H V 0.17

Der Ablauf des Programmes führt zu folgender Ausgabe auf der Systemausgabe:

1 2 3
1 2 5
1 4 5 
itmapa.de - X2H V 0.17

Sofern in der Konstruktorenargumentliste Argumente mit und ohne Standardwerte verwendet werden, ist darauf zu achten, dass die Argumente mit Standardwert möglichst am Ende der Liste definiert werden. Ist dies nicht der Fall, kann (außer beim Einsatz benannter Argumente) nicht entschieden werden, welche Elemente beim Aufruf gemeint sind und die Standardwerte verlieren ihre Hilfskraft.

Hilfskonstruktoren vermeiden

Eine Vielzahl von Hilfskonstruktoren dienen der Konstruktion der Objekte unter Verwendung von Standardwerten. Wie in den vorherigen Abschnitten gesehen, können wir Standardwerte definieren und auch Argumente benannt übergeben. Diese Werkzeuge können uns helfen, die Anzahl der Hilfskonstruktoren merklich zu verringern.

Sehen wir uns zunächst ein Beispiel mit Hilfskonstruktoren an.

class Constructors {
  new A(-1,-2,-3)
  new A(-1,-2)
  new A(-1)
  new A()
}

class A(v1: Int, v2: Int, v3: Int) {
  println("v1: "+v1+" ,v2: "+v2+" ,v3: "+v3)
  
  def this(v1: Int, v2: Int) = this(v1,v2,3)
  def this(v1: Int) = this(v1,2)
  def this() = this(1)
}
itmapa.de - X2H V 0.17

Durch einfache Verwendung von Standardargumenten können in unserem Beispiel alle Hilfskonstruktoren überflüssig gemacht werden.

class Constructors {
  new A(-1,-2,-3)
  new A(-1,-2)
  new A(-1)
  new A()
}

class A(v1: Int = 1, v2: Int = 2, v3: Int = 3) {
  println("v1: "+v1+" ,v2: "+v2+" ,v3: "+v3)
}
itmapa.de - X2H V 0.17

Sichtbarkeit

Nicht immer möchten wir die Konstruktoren einer Klasse für alle Klassen eines Systems sichtbar haben. Möchten wir z.B. den Konstruktor nur aus der eigenen Klasse / aus dem zugehörigen Companion Objekt oder nur innerhalb der Vererbungshirarchie aufrufen, helfen die Sichtbarkeitsmodifizierer private und protected weiter. Mit diesen Sichtbarkeitsmodifizierern können wir auch die Sichtbarkeit auf Packetebene (engl. Package) definieren.

Diese Sichtbarkeitsmodifizierer sind nicht auf Konstruktoren beschränkt, sondern Sie finden auch Verwendung im Zusammenhang mit Funktionen/Methoden oder Feldern (Konstruktoren sind ja auch nur spezielle Methoden/Funktionen).

Stellen wir uns nun die Frage, wo wir die Sichtbarkeitsmodifizierer im Quelltext platzieren sollen, um die Sichtbarkeit der Konstruktoren einzuschränken. Bei Hilfskonstruktoren ist es einfach. Wir stellen den Sichtbarkeitsmodifizierer (wie bei "normalen" Methoden / Funktionen) vor dem einleitenden def des Hilfskonstruktors.

private def this(a: Int) = // Konstruktoren Inhalt
itmapa.de - X2H V 0.20

Nun stellt sich die Frage, an welcher Stelle wir den Sichtbarkeitsmodifizierer bei Primärkonstruktoren platzieren, die bekanntlich kein einleitendes def haben. Die Antwort ist, wir platzieren ihn direkt vor der Parameterliste des Primärkonstruktors. Ist keine Parameterliste (was in etwa einer leeren Parameterliste entspricht) vorhanden, plazieren wir den Modifizierer direkt an der Stelle (oder vor der Stelle) der "imaginären" Parameterliste.

class A private(a: Int) { }
class B protected { }

itmapa.de - X2H V 0.20

Die Bedeutug der Sichtbarkeitsmodifizierer ist gleich der Bedeutung der Sichtbarkeitsmodifizierer für Funktionen und Methoden.

Nachfolgend noch ein vollständiges Beispiel zu Sichtbarkeit und Konstruktoren.

object PrivatePrimaryConstructor {

  def main(args: Array[String]): Unit = {
    new A(42,"-1")     // Error
    new A(4711)        // OK
  }
}

object A {
  def createAnA(): A = {
    new A(42,"42")     // OK
  }
}

class A private(aValue: Int, aString: String) {
  println(aValue+" "+aString)  
  def this(a: Int) = this(a,"Nummer 5 lebt")   // OK
}

itmapa.de - X2H V 0.20