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-Object" 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.

Beispiel
package test

import de.test.Test

class TestClass{
  // Klassenquelltext
}
itmapa.de - X2H V 0.17

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.

object TestObject {
  // Inhalt des Singleton Objektes
}
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.

Dem Thema Konstruktoren ist in diesem Tutorial ein eigenes Kapitel gewidmet.

Singleton Object

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
}
itmapa.de - X2H V 0.17

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 
  }
}
itmapa.de - X2H V 0.17

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)
  }
}   
itmapa.de - X2H V 0.17

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 new zu erzeugen.

  • Alle Parameter, die dem Konstruktor übergeben werden, sind implizit eine val - Variable der Klasse.

  • Case Classes erhalten eine Implementierung der Methoden toString, hashCode und equals.

  • Case Classes können beim Pattern Matching eingesetzt werden.

  • Case Classes können ohne das Schlüsselwort new instanziiert werden.

  • Zum einfachen kopieren von Objekten besitzen Case Classes eine copy Methode.

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
}
itmapa.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)
itmapa.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.

Die Ausführung von 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);
}    
itmapa.de - X2H V 0.17

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);
}     
itmapa.de - X2H V 0.17

Das Disassemblieren mit javap zeigt, dass bei case Klassen eine Fülle von Methoden/Funktionen vom Scala Compiler automatisch generiert werden.

Vererbung

Allgemeines

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 (Variable und Funktionen/Methoden), 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.

Überschreiben und Überschatten

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
}
itmapa.de - X2H V 0.11

Bei der Vererbung werden alle Variablen und Funktionen/Methoden, die nicht als private gekennzeichnet sind an die Subklasse vererbt. Die Subklasse erbt demnach die Eigenschaften der Vaterklasse. Die Subklasse muss sich jedoch nicht mit den Eigenschaften der Vaterklasse abfinden. Die Subklasse kann die Eigenschaften ändern, indem Sie die entsprechenden Variablen überschattet bzw. Funktionen/Methoden überschreibt.

Beim Überschreiben einer Methode definieren wir in der Subklasse eine Methode mit der gleichen Signatur (Name und Argumentenliste) wie in der Vaterklasse. Immer wenn nun auf einem Objekt der Kindklasse die Methode aufgerufen wird, wird nicht mehr die Methode der Vaterklasse verwendet. Diese Eigenschaft der Vaterklasse wurde verändert. In Scala müssen wir (im Gegensatz zu anderen Sprachen) an einer überschreibenden Funktion/Methode zusätzlich das Schlüsselwort override angeben, um den Compiler unsere Absicht mitzuteilen.

Im nachfolgenden Beispiel überschreiben wir in der Klasse SubClass die Methode foo2() der Vaterklasse BaseClass.

object Overload {
  def main(args: Array[String]): Unit = {
    val baseClass = new BaseClass
    baseClass.foo1()
    baseClass.foo2()
    
    val subClass = new SubClass
    subClass.foo1()
    subClass.foo2()
    subClass.foo3()
  }
}

class BaseClass {
  val rrr = 2
  def foo1(): Unit = println("BaseClass: foo1")
  def foo2(): Unit = println("BaseClass: foo2")
}

class SubClass extends BaseClass {
  override def foo2(): Unit = println("SubClass: foo2")
  def foo3(): Unit = println("SubClass: foo3")
}
itmapa.de - X2H V 0.20

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

BaseClass: foo1
BaseClass: foo2
BaseClass: foo1
SubClass: foo2
SubClass: foo3
itmapa.de - X2H V 0.20

Die Angabe des Schlüsselwortes override mag zunächst als Overhead zu anderen Sprachen gesehen werden, bietet uns jedoch folgende Wertvolle Vorteile. Zunächst stellen wir sicher, dass wir auch eine Funktion/Methode einer Basisklasse ändern und nicht versehentlich eine falsche Signatur verwenden. Anderseits schützt uns diese Vorschrift vor ungewolltem Überschreiben, indem wir versehentlich eine Funktion/Methode einer Vaterklasse überschreiben. Das Schlüsselwort override gibt uns dem eine erhöhte Sicherheit während des Programmierens.

Das Überschatten von Variablen in einer Basisklasse erfolgt analog zum Überschreiben einer Funktion/Methode. In der Subklasse definieren wir eine Variable mit dem Namen einer Variable in der Vaterklasse und kennzeichnen diese mit override als überschattend (auch wenn override hier anders übersetzt werden kann).

final

In manchen Fällen möchten wir vermeiden, dass Variable, Funktionen/Methoden oder ganze Klassen abgeleitet werden können. In diesen Fällen hilft uns das Schlüsselwort final weiter. Geben wir dieses Schlüsselwort bei einer Variablen, Funktion/Methode oder Klasse an, ist es nicht möglich dieses von diesem Element abzuleiten. Versuchen wir es doch quittiert das der Compiler mit einer Fehlermeldung und der Quelltext wird nicht compliliert.

Versuchen wir das nachfolgende Beispiel zu complilieren, erhalten wir vom Compiler für die mit // Fehler gekennzeichneten Quelltextzeilen eine Fehlermeldung, dass eine Ableitung an dieser Stelle nicht erlaubt ist.

class A {
  val aValue : Double = 0.0
  final val bValue : Double = 42.0
  
  def aMethod(a: Double) : Double = a * 42.0
  final def bMethod(b: Double) : Double = 43.0 * b
}

final class B 

class C extends A{
  override val aValue : Double = 1.0
  override val bValue : Double = 32.0   // Fehler
  
  override def aMethod(a: Double) : Double = 32.0 + a
  override def bMethod(b: Double) : Double = 44.0 + b   // Fehlerclass D extends B    // Fehler
itmapa.de - X2H V 0.20

Abstrakte Klassen

Abstrakte Klassen sind Traits sehr ähnlich. In abstrakten Klassen können wir Methoden und Variable definieren, die wir nicht ausprogrammieren müssen. Klassen, die von abstrakten Klassen abgeleitet sind, müssen diese Methoden und Variable definieren (implementieren) um nicht selbst als abstrakt zu gelten. Neben den nicht ausprogrammierten Elementen können abstrakte Klassen, wie auch Traits, konkrete, ausprogrammierte Methoden und Variable enthalten. Da abstrakte Klassen nicht vollständig implementiert sind, können von abstrakten Klassen keine Objekte direkt erzeugt werden, sondern es muss eine abgeleitete Klasse gewählt werden, welche die abstrakten Elemente implementiert.

Der wesentliche Unterschied zu Traits besteht darin, dass abstrakte Klassen mit Konstruktoren versehen und damit unterschiedlich initialisiert werden können (bzw. Sie haben immer den Default-Konstruktor). Der Nachteil abstrakter Klassen gegenüber Traits besteht darin, dass abstrakte Klassen abgeleitet werden müssen, also mit Hilfe des Schlüsselwortes extends eingebunden werden. Ein einmischen mit with ist bei abstrakten Klassen nicht möglich. Da Scala keine Mehrfachvererbung unterstützt, kann eine Klasse, welche von einer abstrakten Klasse erbt, von keiner weiteren Klasse erben.

Um eine abstrakte Klasse zu definieren müssen wir der Klasseneinleitung class das Schlüsselwort abstract voranstellen. Nchfolgend ein Beispiel einer abstrakten Klasse MyAbstractClass und deren implementierung in MyAbstractImplementation.

abstract class MyAbstractClass {
  val myAbstractValue: Double 
  val myConcreteValue: Double = 3.0
  
  def myAbstractMethod(d: Double): Double
  def myConcreteMethod(d: Double) = 2.0 * d
}

trait T {}

class MyAbstractImplementation extends MyAbstractClass() with T {
  val myAbstractValue = 3.0  
  def myAbstractMethod(d: Double) = 3.0
}
itmapa.de - X2H V 0.20

Beim genaueren Hinsehen stellen wir fest, dass wir quasi die abstrakten Elemente überschreiben aber, dass in diesem Falle das Schlüsselwort override nicht angegeben werden muss. Eine Angabe des Schlüsselwortes override ist hier optional, kann also, sofern gewünscht angegeben werden.

Nachdem wir eine Implementierung definiert haben können wir ein Objekt der abstrakten Klasse mithilfe der Implementierung definieren.

object Main{
  def main(args: Array[String]): Unit = {
    val t: MyAbstractClass  = new MyAbstractImplementation
  }
}

itmapa.de - X2H V 0.20

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)
  }
}
itmapa.de - X2H V 0.11

Die 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
  }
}
itmapa.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")
  }
}
itmapa.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()
  }
}
itmapa.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._
            

Gleichheit und Identität

Auf Gleichheit und Identität prüfen

Zunächst wollen wir den Unterschied zwischen "gleich" und "identisch" herausarbeiten. Zwei Objektreferenzen (Objekte) sind identisch, wenn Ihre Referenzen auf die gleiche Speicheradresse zeigen. Sind die Objektreferenzen ungleich, sind die entsprechenden Objekte nicht identisch. Die Objekte sind auch dann nicht identisch, wenn die Objekte ansonsten identische (gleiche) Attribute enthalten.

Objekte sind in der Regel gleich, wenn Sie gleiche (identische) Attribute haben. Es ist jedoch nicht erforderlich, dass alle Attribute übereinstimmen. Welche Attribute herangezogen werden hängt von der jeweiligen Implementierung ab.

Nachfolgend eine Grafik, welche den Unterschied zwischen Gleichheit und Identität verdeutlichen soll.

Gleichheit und Identität

Die Rechtecke in der Grafik sollen Speicheradressen im Computer darstellen, auf welche die Objekte obj1, obj2 und obj3 zeigen. Die Länge der einzelnen Objekte legen wir der einfachheitshalber mit 5 Elementen fest. Im Bezug zu Gleichheit und Identität lässt sich Folgendes festhalten:

  • obj1 und obj3 sind identisch, da Sie auf die gleiche Speicheradresse zeigen. Des weiteren sind die Objekte auch gleich (kann auch nicht anders sein, da zwingend alle Attribute übereinstimmen).
  • obj1 und obj2 sind nicht identisch, da Sie auf unterschiedliche Speicheradressen zeigen. Sind jedoch gleich, da der Inhalt übereinstimmt.
  • obj2 und obj3 sind nicht identisch, da Sie auf unterschiedliche Speicheradressen zeigen. Sind jedoch gleich, da der Inhalt übereinstimmt.


Nachfolgend wollen wir uns die Scala Methoden ansehen, mit denen wir Objekte auf Gleichheit und Identität hin untersuchen können.

==

Mit == vergleichen wir Objekte auf Gleichheit. Für Java Entwickler sei hier darauf hingewiesen, dass in Java mit == auf Identität geprüft wird. Hier lauert also ein Fallstrick für Fehler im Programm.

!=

!= ist das Gegenstück zu == und liefert true, wenn die Objekte ungleich sind.

eq

Mit eq prüfen wir, ob zwei Objekte identisch sind.

ne

ne liefert true, wenn zwei Objekte nicht identisch sind.

Für eq und ne gibt es in Scala jedoch eine Einschränkung. Die Methoden sind nur für Klassen definiert, die von scala.AnyRef (java.lang.Object) abgeleitet sind. Bei allen anderen Klassen (z.B. Wertetypen) stehen die Methoden nicht zur Verfügung.

case class AClass(aValue: Int)

class EqÖrNe {
  val aClass1 = new AClass(42)
  val aClass2 = new AClass(42)
  
  val aInt1 = 3
  val aInt2 = 3
  
  val classEq = aClass1 eq aClass2     // OK
  val intEq = aInt1 eq aInt2           // Fehler (compiliert nicht)
}
itmapa.de - X2H V 0.20

Gleichheit und (nicht vorhandene) null Problematik

Wer aus der Java - Programmierung kommt und sich mit der Prüfung auf Gleichheit auseinandergesetzt hat kennt folgende Problematik: Was passiert, wenn die zu vergleichenden Elemente null sein können. Rufen wir die Methode equals auf eine Variable mit dem Wert null auf, erhalten wir eine java.lang.NullPointerException.

Nachfolgend ein Java - Beispiel, an dem dieses Problem nachvollzogen werden kann.

public class JEQDemo {

  public static void main(String[] args) {
    String s1 = null;
    String s2 = "Hello";    
    
    System.out.println(s1.equals(s2));  // NullPointerException
  }
}

itmapa.de - X2H V 0.20

In Java haben wir also die Problematik, dass wir immer sicherstellen müssen, dass Referenzen nicht null sein dürfen, wenn wir Referenzen auf Gleichheit prüfen. Dieser Umstand kann zu nicht unerheblichen zusätzlichen Programmieraufwand führen (und die Prüfung wird unübersichtlicher).

Wie sieht die Sache nun in Scala aus? Ganz einfach. In Scala wird die null Prüfung automatisch vorgenommen. Jeder Aufruf von == (entspricht equals in Java) auf eine null-Referenz führt zum Ergebnis false, sofern das übergebene Objekt nicht null ist.

Nachfolgend ein Beispiel in Scala, wo == auf eine null Referenz aufgerufen wird.

object SEQDemo {
  def main(args: Array[String]): Unit = {
    val s1 = null
    val s2 = "Hello world"
      
    println(s1 == s2)  // kein Problem
  }
}
itmapa.de - X2H V 0.20

Übergeben wir einer null-Referenz eine null-Referenz zur Prüfung auf Gleichheit, erhalten wir als Ergebnis true. Da es vom Typ Null nur eine Instanzvariable null gibt, sind diese nicht nur gleich, sondern auch identisch 1, also auch gleich.

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


codeviolation.de
Unterschiede zwischen Überladen, Überschreiben, Überschatten in der OOP
http://www.codeviolation.de/objektorientierte-programmierung/unterschiede-zwischen-ueberladen-ueberschreiben-ueberschatten-in-der-oop/

______________________________

1 Siehe auch API scala.Null Flagge Großbritanien