Klassen und Objekte
Scala ist eine objektorientierte ProgrammierspracheAufbau einer Klasse
Konstruktoren
Singleton Objekte
Companion object/class
Case Classes
Vererbung
Innere Klassen
Package Objects
Links
Scala ist eine objektorientierte Programmiersprache
Scala ist eine vollständig objektorientierte Programmiersprache. In Scala wird, wie auch in anderen objektorientierten Sprachen, der Bauplan für Objekte mit Hilfe von Klassen definiert. Im Gegensatz zu Java gibt es in Scala keine primitiven Datentypen. In Scala gibt es entsprechend vollwertige Objekte, die deren Aufgaben übernehmen.
Eingeleitet wird eine Klasse im Quelltext mit einem der beiden Schlüsselwörtern
class oder object. Klassen, die mit dem Schlüsselwort
class eingeleitet werden, sind Klassen von denen beliebig viele
Instanzen erzeugt werden können. Statische Methoden bzw. Variablen gibt
es in Scala nicht. Dort schafft die Einleitung einer Klasse mit dem Schlüsselwort
object Abhilfe. Von Klassen, die mit dem Schlüsselwort object
eingeleitet werden, existiert höchstens ein einziges Objekt (keins, wenn es
noch nicht verwendet wurde) und wird beim erstmaligen Gebrauch initialisiert.
In Scala werden diese Objekte als "Singleton-Objekte" bezeichnet.
Aufbau einer Klasse
Der Aufbau des Quelltextes einer Klasse entspricht dem Aufbau in Java.
Sofern die Klasse sich nicht im "default-Package" befindet, wird
das Package angegeben, indem sich die Klasse befindet. Anschließend
werden die importierten Klassen angegeben. Danach folgt die Einleitung der
Klasse mit dem Schlüsselwort class oder object
gefolgt vom Namen der Klasse. Abschließend wird im Rumpf von
geschweiften Klammern der Quelltext der Klasse definiert.
Innerhalb des Rumpfes werden die Variablen und Methode der Klasse definiert.
package test
import de.test.Test
class TestClass{
// Klassenquelltext
}
Konstruktoren
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
}
timpt.de - X2H V 0.9
Der Rumpf einer Klasse stellt den Inhalt des Primärkonstruktors dar. Dieser wird dementsprechend bei der Konstruktion einer Klasse aufgerufen.
Dem Thema Konstruktoren ist in diesem Tutorial ein eigenes Kapitel gewidmet.
Singleton Objekte
Scala kennt keine statischen Methoden und Variablen. Abhilfe schaffen
hier sogenannte Singleton Objekte. Die Definition eines
Singleton Objektes erfolgt ähnlich der Definition einer Klasse.
Hauptunterscheidungsmerkmal ist, dass eine Singleton Objekt Definition
mit dem Schlüsselwort object statt dem Schlüsselwort
class eingeleitet wird.
object TestObject {
// Inhalt des Singleton Objektes
}
Da Singleton Objekte nicht mit new
angelegt werden, können diese auch nicht mit Parameter
über einen Konstruktor initialisiert werden. Die Definition
von Funktionen/Methoden und Variablen innerhalb eines Singleton
Objektes ist identisch der Definition innerhalb von Klassen.
object ScalaObject {
val b = 5
def test(a: Double) : Double = {
3.* + b
}
}
Companion object/class
Scala Klassen kennen keine statischen Elemente. Abhilfe schafft hier eine Zusammenfassung von Scala Singleton Objekten und Klassen. Definiert man ein Singleton Objekt und eine Klasse mit gleichem Namen, können diese gegenseitig auf spezielle weise aufeinander zugreifen. Beispielsweise können diese gegenseitig auf ihre privaten Elemente zugreifen. Voraussetzung ist jedoch, dass das Singleton Objekt und die Klasse in der gleichen Quelltextdatei definiert werden.
Bei einer derartigen Kombination bezeichnet man das Singleton Objekt auch als companion object (Begleitobjekt) und die Klasse als companion class (Begleitklasse). Singleton Objekte, die keine companion class Klasse haben, werden als standalone object (Einzelobjekt) bezeichnet.
Die Initialisierung eines Singleton Objektes findet bei der ersten Verwendung desselben statt.
Das nachfolgende Beispiel zeigt ein companion object
FussballSpiel mit der zugehörigen companion class
FussballSpiel.
object FussballSpiel {
val dauer = 90
def restSpielZeit(spielZeit: Int): Int= {
dauer - spielZeit
}
}
class FussballSpiel{
var toreHeim = 0
var toreGast = 0
var spielZeit = 0
def torDifferenz(): Int={
Math.abs(toreHeim-toreGast);
}
def restSpielZeit(): Int= {
FussballSpiel.restSpielZeit(spielZeit)
}
}
Case Classes
Case classes bieten dem Programmierer einige Annehmlichkeiten, die implizit vom Compiler vorgenommen werden:
-
Sie besitzen eine Factory-Methode mit dem Namen der Klasse.
Es ist daher nicht notwendig Cases Classes mit dem Schlüsselwort
newzu erzeugen.
-
Alle Parameter, die dem Konstruktor übergeben werden, sind
implizit eine
val- Variable der Klasse.
-
Case Classes erhalten eine Implementierung der Methoden
toString,hashCodeundequals.
-
Case Classes können beim Pattern Matching eingesetzt werden.
-
Case Classes können ohne das Schlüsselwort
newinstanziiert werden.
-
Zum einfachen kopieren von Objekten besitzen Case Classes eine
copyMethode.
Eine case class wird durch voranstellen des Schlüsselwortes case
vor dem einleitenden Schlüsselwortes class definiert.
Das nachfolgende Beispiel zeigt die Definition einer case class Person:
case class Person (firstName : String, lastName : String, age : Int){
def isAdult : Boolean = if (age >= 18) true else false
}timpt.de - X2H V 0.5
Ein Unterschied zwischen "normalen" und case Klassen lässt
sich auch am Ergebnis des Kompiliervorgangs betrachten. Kompilieren wir hierzu
folgenden Scala Quelltext (der Name der zu kompilierenden Datei kann frei gewählt werden):
class A1(v1: Int, v2: Double) case class A2(v1: Int, v2: Double)timpt.de - X2H V 0.10
Mit Ausnahme des case Schlüsselwortes sind die Klassen
A1 und A2 identisch. Sieht man sich die erzeugten
.class Dateien an, sieht man, das zur Klasse A2
zusätzlich eine .class Datei mit dem Namen
A2$.class erzeugt wurde. Diese Datei enthält
das zur case class automatisch generierte Begleitobjekt
(engl. companion object). Der Unterschied zwischen A1 und A2
kann durch die Verwendung des Java Disassembler javap sichtbar gemacht
werden. Die Verwendung von javap ist möglich, da Scala
Klassen zu gewöhnlichen Klassen für die JVM kompiliert werden,
die wie gewöhnliche Java Klasses disassembliert werden können.
javap A1 zeigt folgenden Inhalt für A1:
Compiled from "ScalaTest.scala"
public class A1 extends java.lang.Object implements scala.ScalaObject{
public A1(int, double);
}
Die Ausführung von javap A2 und javap A2$
zeigt das Ergebnis der Kompilierung der Klasse A2:
Compiled from "ScalaTest.scala"
public class A2 extends java.lang.Object implements scala.ScalaObject,scala.Product,java.io.Serializable{
public static final scala.Function1 tupled();
public static final scala.Function1 curry();
public static final scala.Function1 curried();
public scala.collection.Iterator productIterator();
public scala.collection.Iterator productElements();
public double copy$default$2();
public int copy$default$1();
public int v1();
public double v2();
public A2 copy(int, double);
public int hashCode();
public java.lang.String toString();
public boolean equals(java.lang.Object);
public java.lang.String productPrefix();
public int productArity();
public java.lang.Object productElement(int);
public boolean canEqual(java.lang.Object);
public A2(int, double);
}
Compiled from "ScalaTest.scala"
public final class A2$ extends scala.runtime.AbstractFunction2 implements scala.ScalaObject{
public static final A2$ MODULE$;
public static {};
public scala.Option unapply(A2);
public A2 apply(int, double);
public java.lang.Object apply(java.lang.Object, java.lang.Object);
}
Das Disassemblieren mit javap zeigt, dass bei case
Klassen eine Fülle von Methoden/Funktionen vom Scala Compiler automatisch
generiert werden.
Vererbung
Neben Ihren funktionalen Eigenschaften ist Scala eine objektorientierte Programmiersprache. Und wie in allen objektorientierten Programmiersprachen spielt die Vererbung eine wesentliche Rolle. Vererbt eine Klasse Ihre Eigeneschaften, so spricht man auch vom Ableiten von einer Klasse. Der Vorgang des Ableitens wird auch als Spezialisierung und der Vorgang der Bildung einer Superklasse wird als Generalisierung bezeichnet.
Klassen, die von anderen Klassen Eigenschaften erben, werden wie folgt bezeichnet:
- Subklasse
- abgeleitete Klasse
- Unterklasse
- Kindklasse
Dem entgegengesetzt werden Klassen, die Ihre Eigenschaften vererben wie folgt bezeichnet:
- Vaterklasse
- Superklasse
- Basisklasse
- Oberklasse
- Elternklasse
Die unterschiedlichen Begriffe deuten nicht auf unterschiedliche Bedeutungen hin, sondern sind synonym zu verstehen.
Um in Scala von einer Klasse abzuleiten, wird das Schlüsselwort extends
verwendet. Zunächst erfolgt die Einleitung der Klasse mit dem Schlüsselwort class,
gefolgt vom Namen der Klasse und ggf. einer Parameterliste. Bevor nun die geschweifte Klammer den
Inhalt der Klasse einleitet, wird das Schlüsselwort extends gefolgt
vom Namen der abzuleitenden Klasse angegeben. Das nachfolgende Beispiel zeigt die Ableitung einer
Klasse A durch eine Klasse namens B.
class B (arg: Int) extends A {
// Inhalt der Klasse
}
timpt.de - X2H V 0.11
Innere Klassen
Innerhalb einer Klasse können weitere Klassen definiert werden. Derartige Klassen können jedoch
nur im jeweiligen Geltungsbereich der Definition verwendet werden. Wird zum Beispiel innerhalb einer Methode
eine Klasse benötigt, die an keiner anderen Stelle benötigt wird, definieren wir die Klasse
einfach innerhalb dieser Methode. Im nachfolgenden Beispiel definieren wir eine Klasse Person
innerhalb der Methode startWorking der Klasse Outer. Anschließend
definieren wir eine Instanz dieser Klasse und geben deren String Repräsentation auf
der Systemausgabe aus. Bei der Verwendung println(person) wird deren toString()
Methode aufgerufen, welche wir in Person überschrieben haben.
object MyMain {
def main(args: Array[String]) {
new Outer().startWorking
}
}
class Outer{
def startWorking() {
class Person(firstName: String, lastName: String) {
override def toString() = lastName+", "+firstName
}
val person = new Person("Hans","Maier")
println(person)
}
}
timpt.de - X2H V 0.11
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
Maier, Hans
Innerhalb der Methode können wir die Klasse Person erst nach der Position
der Definition verwenden.
Benötigen wir eine Klasse innerhalb einer Klasse (und in keiner anderen), können
wir die Klasse auch außerhalb einer Methode im Klassenrumpf der Äußeren definieren.
Im nachfolgenden Beispiel wurde die Klasse Person außerhalb der Methode
startWorking im Klassenrumpf definiert. Da der Klassenrunpf dem Inhalt des Primärkonstruktors
entspricht, ist die Klasse Person Bestandteil des Konstruktors und steht allen Methoden
der Klasse Outer zur Verfügung.
object MyMain {
def main(args: Array[String]) {
new Outer().startWorking
}
}
class Outer{
println(new Person("Michael","Mustermann"))
def startWorking() {
val person = new Person("Hans","Maier")
println(person)
}
class Person(firstName: String, lastName: String) {
override def toString() = lastName+", "+firstName
}
}
timpt.de - X2H V 0.11
Der Ausführung des Programmes führt zu folgender Ausgabe auf der Systemausgabe:
Mustermann, Michael
Maier, Hans
Package Objects
Mit Scala 2.8 hielten die sogenannten Package Objects Einzug in die Sprache. Für jedes Package können wir ein Package Object definieren, das dann im gesamten Package "sichtbar" ist. Package Objects eignen sich insbesondere zur Definition von:
- Typen
- Feldern
- Methoden / Funktionen
- Impliziten Konvertierungen
die im gesamten Package, ohne gesonderten import verwendet werden können.
Die Definition eines Packages Objects beginnt mit package object
gefolgt vom Namen des Packages. Im Anschluss erfolgt wie bei anderen Objekten (Klassen, Traits)
die Definition des Objekt Inhaltes. Gespeichert wird der Quelltext hierarchisch im entsprechenden
Verzeichnis unter den Namen package.scala
Das nachfolgende Beispiel zeigt eine einfache Definition eines Package Objects.
package object mypackage {
def printMyPackageObject() {
println("Hey, I'm a Package Object")
}
}
timpt.de - X2H V 0.11
Im nachfolgenden Beispiel wird die einfache Anwendung des Package Objects gezeigt.
package mypackage
object MainClass {
def main(args: Array[String]) : Unit = {
printMyPackageObject()
}
}
timpt.de - X2H V 0.11
Die Ausführung des Programmes führt zur erwarteten Ausgabe auf der Systemausgabe:
Hey, I'm a Package Object
Möchten wir das Package Object aus anderen Packages zugreifen, besteht eine Möglichkeit darin, den voll qualifizierten Namen des Packages gefolgt vom gewünschten Methodennamen anzugeben.
mypackage.printMyPackageObject()
Eine weitere Möglichkeit besteht darin, dass Package Object mit Hilfe einer import-Anweisung
in den Sichtbarkeitsbereich zu holen. Dazu geben wir nach dem import-Statement den
Package Namen gefolgt von Punkt und Unterstrich an.
import mypackage._
Links
Heiko Seeberger
Scala 2.8: Package Scopes und Package Objects Fortsetzung, Teil 2
http://it-republik.de/jaxenter/artikel/Scala-2.8-Package-Scopes-und-Package-Objects-2816.html