Sichtbarkeit
- Sichtbarkeitsmodifizierer
- Packages
- Elelemente (Klassen ...) importieren
- Sichtbarkeit ausweiten bzw. einschränken
- Zusammenfassung
Stand: 19. September 2013
Sichtbarkeitsmodifizierer
Wer aus der Java-Programmierung kommt, dem dürfte beim Studium von Scala Quelltexten
auffallen, das sehr wenig Sichtbarkeitsmodifizerer wie public
,
private
oder protected
verwendet werden.
Wird in Scala kein Sichtbarkeitsmodifizierer angegeben, so handelt es sich um
die Sichtbarkeitsstufe public
(öffentlich), welche für alle Klassen sichtbar
ist. Das Weglassen des Sichtbarkeitsmodifizierers in Java würde zur Default-Sichtbarkeit
führen, die so in Scala nicht definiert ist. Die Sichtbarkeitsstufe public
ist in Scala Standard. Ein Sichtbarkeitsmodifizierer public
wird in Scala
nicht angegeben. Eine Angabe würde sogar zu einem Compiler-Fehler führen.
Für Elemente, die mit der Sichtbarkeitsstufe private
gekennzeichnet sind,
gilt, dass diese Elemente nur innerhalb Ihrer eigenen Klasse sichtbar sind.
Die Sichtbarkeitsstufe protected
definiert eine Sichtbarkeit der Elemente
innerhalb der eigenen Klasse und in allen von Ihr abgeleiteten Klassen. Im Unterschied zu Java
sind mit protected
gekennzeichnete Elemente (ohne weitere Definition)
nicht innerhalb Ihres eigenen Packages für andere Klassen sichtbar.
Bei den Sichtbarkeitsmodifizierern private
und protected
kann innerhalb folgenden, rechteckigen Klammern, die
Sichtbarkeit feiner eingestellt bzw. ausgeweitet werden. So ist es z.B. möglich
die Sichtbarkeit auf eine fest definierte andere Klasse (oder ein anderes Package) auszuweiten (private[X] def ...
).
Auch das Einschränken der Sichtbarkeit auf das eigene Objekt ist mit private[this] ...
möglich, sodass andere Objekte der gleichen Klasse nicht auf das jeweilige Element zugreifen
können.
Insgesamt lässt sich feststellen, dass die Sichtbarkeit in Scala wesentlich feiner eingestellt werden kann, als dies bei Java der Fall ist.
class ScalaVisibility { // Sichtbarkeitsstufe public: von überall aufrufbar def myPublicFunction : Int = 1 // Sichtbarkeitsstufe protected: Nur für die eigene und abgeleitete // Klassen sichtbar protected def myProtectedFunction : Int = 2 // Sichtbarkeitsstufe private: nur innerhalb der eigenen Klasse // sichtbar private def myPrivateFunction : Int = 3 } class myTestClass{ val visio = new ScalaVisibility println(visio.myPublicFunction) // OK println(visio.myProtectedFunction) // Fehler println(visio.myPrivateFunction) // Fehler } class myTestClass2 extends ScalaVisibility{ val visio = new ScalaVisibility println(visio.myPublicFunction) // OK println(visio.myProtectedFunction) // Fehler println(visio.myPrivateFunction) // Fehler println(myProtectedFunction) // OK println(myPrivateFunction) // Fehler }
Packages
Selbst in kleineren Projekten steigt die Anzahl der verwendeten Klassen, Traits, Singleton Objekte ... schnell auf ein Maß an, dass die Übersichtlichkeit darunter leidet. Um nun den Überblick zu behalten, bietet es sich an, Klassen in Packages zu organisieren. Die Package-Struktur entspricht dabei der Struktur aktueller Dateisysteme, wo eine Baumstruktur aufgebaut wird und die Elemente in den Unterschiedlichen Ebenen abgelegt werden. Alle Elemente, die sich in einer Ebene befinden, haben dabei eine zusammengehörige Aufgabe.
Auch wenn in Scala weit aus flexibler mit Packages gearbeitet werden kann, bietet es sich für den Anfang an, die Vorgehensweise von Java zu übernehmen und jede Klasse (Trait ...) am Anfang des Quelltextes einem Package zuzuordnen.
package de.scalatutorial.berechnung.helfer
class Rechenknecht {
// Inhalt der Klasse
}
Die Definition eines Packages beginnt mit dem Schlüsselwort package
gefolgt vom Package-Namen. Die Namen der einzelnen Baumebenen werden durch einen Punkt voneinander
getrennt. Im obigen Beispiel definieren wir ein Package in der vierten Ebene. Vorausgesetzt unser
Quelltext Root Verzeichnis entspricht C:\projekte\scalatutorial\src
entspricht die obige
Package Definition dem Verzeichnis C:\projekte\scalatutorial\de\scalatutorial\berechnunug\helfer
.
Gibt man keine Package-Definition im Quelltext an, so befinden sich die enthaltenen Elemente im sogenannten
Default-Package. Auch wenn die meisten Beispiele hier auf scalatutorial.de
ohne Package Angabe
gezeigt werden (dies dient der Kürze und Verständlichkeit), wird von dessen Verwendung
in realen Projekten abgeraten.
Eine Konvention besagt, dass Package Namen so gewählt werden sollen, dass Sie der umgekehrten
qualifizierten Schreibweise einer zugehörigen Web-Präsenz entsprechen. Dem entspricht,
dass die Beispiele auf scalatutorial.de
als Basis Package de.scalatutorial
haben sollten. Hintergrund dieser Konvention ist, dass damit sichergestellt werden kann,
dass die Verwendung von Klassen (Traits ...) aus verschiedenen Quellen, nicht zu Namenskonflikten
führen kann. Der vollständig qualifizierende Namen einer Klasse besteht zum Beispiel
aus Package Namen und Klassen Namen. Der voll qualifizierende Name der Klasse aus obigem Beispiel
ist somit de.scalatutorial.berechnung.helfer.Rechenknecht
. Auch wenn diese Konvention
nicht für alle Projekte verwenden kann, sollte man Sie jedoch in Projekten anwenden, dessen
Quellen man für andere Projekte (anderer Personen, Teams) zur Verfügung stellen möchte.
Elemente (Klassen ...) importieren
Um Klassen (Objekte ...) verwenden zu können, müssen diese entweder mit Ihrem voll qualifizierenden Namen angegeben werden oder zuvor in den Sichtbarkeitsbereich für die entsprechenden Quelltextteil geholt werden.
Eine Klasse kommt in Scala durch einen der folgenden zwei Mechanismen in den Sichtbarkeitsbereich, so dass wir diese ohne voll qualifizierenden Namen verwenden können.
- Expliziter Import
- Automatischer Import
Expliziter Import
Die meisten Elemente einer Scala-Blibliothek wie Klassen oder Traits können wir nicht direkt,
ohne Angabe des Packages, in unserem Quelltext verwenden.
Beispielsweise können wir keine Instanz der Klasse scala.collection.mutable.ListBuffer
direkt in der REPL (Scala Interpreter) erzeugen.
scala> val listBuffer = new ListBuffer[String] <console>:7: error: not found: type ListBuffer val listBuffer = new ListBuffer[String]
Um dies zu tun, können wir den voll qualifizierten Namen, d.h. Package-Name plus Klassenname, verwenden.
scala> val listBuffer = new scala.collection.mutable.ListBuffer[String] listBuffer: scala.collection.mutable.ListBuffer[String] = ListBuffer()
Jedes Mal den voll qualifizierten Namen anzugeben ist nicht nur aufwendig, sondern führt
auch zu schwerer lesbaren Quelltext.
Möchten wir nun die Klasse ListBuffer
, ohne Angabe des Packages, verwenden, müssen wir diese
zunächst in den Sichtbarkeitsbereich unseres aktuellen Kontextes (hier REPL) holen.
Helfen tut uns hierbei das Schlüsselwort import
, mit
dem wir Klassen in den Sichtbarkeitsbereich holen können. Um nun die Klasse
ListBuffer
in den Sichtbarkeitsbereich zu holen, verwenden wir
import
, gefolgt vom Namen der zu importierenden Klasse.
Da noch keine (hier helfenden) import
-Anweisungen ausgeführt worden sind,
müssen wir den voll qualifizierten Namen der Klasse angeben. Nachdem wir dies getan haben, können wir von der
Klasse ListBuffer
Instanzen (Objekte), ohne Angabe des Packages, erzeugen.
scala> import scala.collection.mutable.ListBuffer import scala.collection.mutable.ListBuffer scala> val listBuffer = new ListBuffer[String] listBuffer: scala.collection.mutable.ListBuffer[String] = ListBuffer()
Möchten wir mit mehreren Elementen (z.B. Klassen) eines Packages arbeiten, können wir alle Elemente eines Paketes mit dem Namen des Packages, gefolgt vom Unterstrich, in den aktuellen Sichtbarkeitsbereich holen.
scala> import scala.collection.mutable._ import scala.collection.mutable._ scala> val aLinkedList = new LinkedList[String] aLinkedList: scala.collection.mutable.LinkedList[String] = LinkedList()
Möchten wir mehrere Elemente eines Packages, aber nicht alle, importieren,
können wir das in Scala mit einem einzigen import
-Statement tun.
Dazu geben wir wie gewohnt zunächst das import
-Statement gefolgt
vom Packagenamen und einem abschließenden Punkt an. Danach geben wir
die zu importierenden Elemente des Packages in geschweiften Klammern durch
ein Komma getrennt an. Im nachfolgenden Beispiel importieren wir die Klassen
ListBuffer
, HashMap
und HashSet
des Packages scala.collection.mutable
. Alle anderen Elemente (Klassen)
werden durch dieses import
-Statement nicht importiert.
scala> import scala.collection.mutable.{ListBuffer, HashMap, HashSet} import scala.collection.mutable.{ListBuffer, HashMap, HashSet}
Automatischer Import
Einige Elemente (Klassen ...) sind so elementar, dass sie nicht explizit importiert werden müssen. Elemente, die in folgenden Packages (Singleton Objekt) definiert sind werden automatisch importiert und stehen somit ohne explizitem Import immer zur Verfügung.
- Package: scala
- Singleton Object: Scala.Prefed
- Package: java.lang
Sichtbarkeit ausweiten bzw. einschränken
Mithilfe der Schlüsselwörter protected
und private
können wir die Sichtbarkeit von Klassen/Objekten und Methoden/Funktionen
(auch Konstruktoren) feiner einstellen als oben einleitend erklärt. Um dies zu erreichen,
geben wir in eckigen Klammern an, wie wir die Sichtbarkeit verfeinern wollen.
Verfeinerung von private
mittels Paketangabe
Geben wir in den rechteckigen Klammern hinter private
einen Paketnamen an,
so bedeutet dies, dass wir die Sichtbarkeit auf das angegebene Paket und all dessen
Unterpakete erweitern. Zu beachten ist hier, dass das Element sich im angegebenen
Paket oder in einem dessen Unterpakete der entsprechenden Klasse befindet.
Pakete, die nicht im Paketpfad der aktuellen Klasse liegen, können nicht zur Verfeinerung herangezogen werden.
Weiter zu beachten ist, dass wir nur den Namen des "tiefsten" Paketes angeben und
nicht den voll qualifizierenden Namen des Paketes. Sehen wir uns dazu Beispiele an:
private[apackage] def aMethod = ... class AClass private[apackage] = ...
Auf die Methode aMethod
können wir aus der eigenen Klasse (bzw.
Companion Object) zugreifen,
sowie aus allen Klassen (Objekten) die sich im Paket apackage
und deren
Unterpakete befinden. Den Primärkonstruktor
der Klasse AClass
können wir aus der eigenen Klasse (bzw. Companion Object)
und aus allen Elementen im und unterhalb des Pakets apackage
aufrufen.
Verfeinerung von private
mittels Klassenangabe
Eine Klasse in rechteckigen Klammern bedeutet, dass wir die Sichtbarkeit des Elements
auf die entsprechende Klasse verfeinern. Zu beachten ist hier, dass die zu erweiternde
Klasse hier innerhalb der angegebenen Klasse definiert ist. Klassen, welche die aktuelle Klasse nicht
umgeben, sind bei private
zur Verfeinerung der Sichtbarkeit nicht erlaubt.
Sehen wir uns nun ein Beispiel an, wo wir die Konstruktion eines Objektes durch Einschränkung des Konstruktors verfeinern.
class B { val myC = new C() val myD = new myC.D() class C { val myD = new D() val myE = new myD.E() // Fehler val myF = new myD.F() // OK class D { val myInnerE = new E() // OK class E private[D](){} class F() {} } } }
Im Beispiel ist es möglich, ein Objekt der KIasse F
außerhalb der Klasse
D
zu erzeugen. Die Einschränkung der Klasse E
führt dazu, dass
das Objekt nicht außerhalb der Klasse D
erzeugt werden kann
(es gibt nur den Primärkonstruktor und keine Factories).
Im nachfolgenden Beispiel können die E
-Objekte zwar außerhalb von D
erzeugt werden jedoch ist der Zugriff auf die Methode foo
versperrt.
class B { val myC = new C() val myD = new myC.D() class C { val myD = new D() val myE = new myD.E() // OK myE.foo() // Fehler val myF = new myD.F() // OK myF.foo() // OK class D { val myInnerE = new E() // OK class E() { private[D] def foo(): Unit = println("I do something") } class F() { def foo(): Unit = println("I do something") } } } }
Vollsperrung mit private[this]
Geben wir in den rechteckigen Klammern das Schlüsselwort this
an, so
bedeutet dies, dass das Element nur aus dem eigenem Objekt aus aufgerufen werden kann.
Der Zugriff ist also auf die eigene Referenz begrenzt. Ein Zugriff aus Referenzen der
gleichen Klasse bzw. aus dem Companion Objekt
ist nicht möglich.
object Vollsperrung { def main(args: Array[String]): Unit = { val test1 = new TestClass() val test2 = new TestClass() } } class TestClass { private def foo1(): Unit = println("Foo 1") private[this] def foo2(): Unit = println("Foo 2") def testIt1(that: TestClass): Unit = that.foo1() // OK def testIt2(that: TestClass): Unit = that.foo2() // Fehler }
Die Verfeinerung mit [this]
kann auch bei Konstruktoren angegeben werden. Geben wir diese
Verfeinerung bei einem Primärkonstruktor an, so müssen wir nicht private
Hilfskonstruktoren anbieten, da ansonsten
eine Objekterzeugung unmöglich wird.
Zusammenfassung
Die nachfolgende Tabelle fasst die wichtigsten Regeln zur Sichtbarkeit zusammen.
Modifizierer | Bedeutung |
ohne (kein Modifizierer) | Standardsichtbarkeit öffentlich |
private
|
Nur sichtbar für Elemente dieser Klasse |
protected
|
Sichtbar in der aktuellen Klasse und allen abgeleiteten Klassen |
private[package]
|
Sichtbar in der aktuellen Klasse und allen abgeleiteten Klassen im selben Package oder Unterpackage |
private[class]
|
Sichtbar in der aktuellen Klasse und allen abgeleiteten Klassen umgeben von der angegebenen Klasse |
private[this]
|
Nur Sichtbar innerhalb der selben Referenz |