Scala Language
Klassen en objecten
Zoeken…
Syntaxis
-
class MyClass{} // curly braces are optional here as class body is empty
-
class MyClassWithMethod {def method: MyClass = ???}
-
new MyClass() //Instantiate
-
object MyObject // Singleton object
-
class MyClassWithGenericParameters[V1, V2](vl: V1, i: Int, v2: V2)
-
class MyClassWithImplicitFieldCreation[V1](val v1: V1, val i: Int)
-
new MyClassWithGenericParameters(2.3, 4, 5)
of met een ander type:new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
-
class MyClassWithProtectedConstructor protected[my.pack.age](s: String)
Instantieklasse-instanties
Een klasse in Scala is een 'blauwdruk' van een klasse-instantie. Een exemplaar bevat de status en het gedrag zoals gedefinieerd door die klasse. Een klasse aangeven:
class MyClass{} // curly braces are optional here as class body is empty
Een exemplaar kan worden gestart met behulp van een new
trefwoord:
var instance = new MyClass()
of:
var instance = new MyClass
Haakjes zijn optioneel in Scala voor het maken van objecten uit een klasse zonder constructor. Als een klassenbouwer argumenten aanneemt:
class MyClass(arg : Int) // Class definition
var instance = new MyClass(2) // Instance instantiation
instance.arg // not allowed
Hier vereist MyClass
één Int
argument, dat alleen intern voor de klas kan worden gebruikt. arg
kan niet worden gebruikt buiten MyClass
tenzij het als een veld wordt aangegeven:
class MyClass(arg : Int){
val prop = arg // Class field declaration
}
var obj = new MyClass(2)
obj.prop // legal statement
Als alternatief kan het in de constructor openbaar worden gemaakt:
class MyClass(val arg : Int) // Class definition with arg declared public
var instance = new MyClass(2) // Instance instantiation
instance.arg //arg is now visible to clients
Klasse instantiëren zonder parameter: {} vs ()
Laten we zeggen dat we een klasse MyClass hebben zonder constructorargument:
class MyClass
In Scala kunnen we het instantiëren met behulp van onderstaande syntaxis:
val obj = new MyClass()
Of we kunnen eenvoudig schrijven:
val obj = new MyClass
Maar als er niet op wordt gelet, kan optionele haakjes in sommige gevallen onverwacht gedrag veroorzaken. Stel dat we een taak willen maken die in een afzonderlijke thread moet worden uitgevoerd. Hieronder staat de voorbeeldcode:
val newThread = new Thread { new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
}
newThread.start // prints no output
We denken misschien dat deze voorbeeldcode, indien uitgevoerd, de Performing task.
zal afdrukken Performing task.
, maar tot onze verbazing zal het niets afdrukken. Laten we kijken wat hier gebeurt. Als je beter kijkt, hebben we accolades {}
, direct na de new Thread
. Het creëerde een anonieme klasse die Thread
uitbreidt:
val newThread = new Thread {
//creating anonymous class extending Thread
}
En toen hebben we in de kern van deze anonieme klasse onze taak gedefinieerd (opnieuw een anonieme klasse maken die Runnable
interface implementeert). Dus we hebben misschien gedacht dat we de public Thread(Runnable target)
constructor hebben gebruikt, maar in feite (door optionele ()
negeren) hebben we de public Thread()
constructor gebruikt zonder dat er iets is gedefinieerd in de methode body of run()
. Om het probleem op te lossen, moeten we haakjes gebruiken in plaats van accolades.
val newThread = new Thread ( new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
)
Met andere woorden, hier {}
en ()
zijn niet uitwisselbaar .
Singleton & begeleidende objecten
Singleton-objecten
Scala ondersteunt statische leden, maar niet op dezelfde manier als Java. Scala biedt een alternatief voor dit Singleton Objects . Singleton-objecten zijn vergelijkbaar met een normale klasse, behalve dat ze niet kunnen worden geïnstantieerd met het new
trefwoord. Hieronder is een voorbeeld van een singleton-klasse:
object Factorial {
private val cache = Map[Int, Int]()
def getCache = cache
}
Merk op dat we het object
trefwoord hebben gebruikt om het singleton object te definiëren (in plaats van 'class' of 'trait'). Omdat singleton-objecten niet kunnen worden geïnstantieerd, kunnen ze geen parameters hebben. Toegang tot een singleton-object ziet er als volgt uit:
Factorial.getCache() //returns the cache
Merk op dat dit er precies zo uitziet als toegang krijgen tot een statische methode in een Java-klasse.
Begeleidende objecten
In Scala kunnen singleton-objecten de naam van een overeenkomstige klasse delen. In een dergelijk scenario wordt het singleton-object een begeleidend object genoemd . Onder de klasse Factorial
wordt bijvoorbeeld gedefinieerd, en eronder wordt een begeleidend object (ook wel Factorial
genoemd) gedefinieerd. Volgens conventie worden begeleidende objecten in hetzelfde bestand gedefinieerd als hun begeleidende klasse.
class Factorial(num : Int) {
def fact(num : Int) : Int = if (num <= 1) 1 else (num * fact(num - 1))
def calculate() : Int = {
if (!Factorial.cache.contains(num)) { // num does not exists in cache
val output = fact(num) // calculate factorial
Factorial.cache += (num -> output) // add new value in cache
}
Factorial.cache(num)
}
}
object Factorial {
private val cache = scala.collection.mutable.Map[Int, Int]()
}
val factfive = new Factorial(5)
factfive.calculate // Calculates the factorial of 5 and stores it
factfive.calculate // uses cache this time
val factfiveagain = new Factorial(5)
factfiveagain.calculate // Also uses cache
In dit voorbeeld gebruiken we een cache
om een faculteit van een getal op te slaan om de berekeningstijd voor herhaalde getallen te besparen.
Hier is object Factorial
een begeleidend object en is class Factorial
de bijbehorende begeleidende klasse. Companion-objecten en klassen hebt toegang tot elkaars private
leden. In het bovenstaande voorbeeld heeft de Factorial
klasse toegang tot het cache
lid van het bijbehorende object.
Merk op dat een nieuwe instantie van de klasse nog steeds hetzelfde bijbehorende object gebruikt, dus elke wijziging in lidvariabelen van dat object zal worden overgenomen.
Voorwerpen
Terwijl klassen meer op blauwdrukken lijken, zijn objecten statisch (dat wil zeggen dat ze al zijn geïnstantieerd):
object Dog {
def bark: String = "Raf"
}
Dog.bark() // yields "Raf"
Ze worden vaak gebruikt als aanvulling op een klas, ze stellen je in staat om te schrijven:
class Dog(val name: String) {
}
object Dog {
def apply(name: String): Dog = new Dog(name)
}
val dog = Dog("Barky") // Object
val dog = new Dog("Barky") // Class
Exemplaartype controleren
Type check : variable.isInstanceOf[Type]
Met patroonovereenkomst (niet zo handig in deze vorm):
variable match {
case _: Type => true
case _ => false
}
Zowel isInstanceOf
als patroonaanpassing controleert alleen het type van het object, niet de generieke parameter (geen typereificatie), behalve arrays:
val list: List[Any] = List(1, 2, 3) //> list : List[Any] = List(1, 2, 3)
val upcasting = list.isInstanceOf[Seq[Int]] //> upcasting : Boolean = true
val shouldBeFalse = list.isInstanceOf[List[String]]
//> shouldBeFalse : Boolean = true
Maar
val chSeqArray: Array[CharSequence] = Array("a") //> chSeqArray : Array[CharSequence] = Array(a)
val correctlyReified = chSeqArray.isInstanceOf[Array[String]]
//> correctlyReified : Boolean = false
val stringIsACharSequence: CharSequence = "" //> stringIsACharSequence : CharSequence = ""
val sArray = Array("a") //> sArray : Array[String] = Array(a)
val correctlyReified = sArray.isInstanceOf[Array[String]]
//> correctlyReified : Boolean = true
//val arraysAreInvariantInScala: Array[CharSequence] = sArray
//Error: type mismatch; found : Array[String] required: Array[CharSequence]
//Note: String <: CharSequence, but class Array is invariant in type T.
//You may wish to investigate a wildcard type such as `_ <: CharSequence`. (SLS 3.2.10)
//Workaround:
val arraysAreInvariantInScala: Array[_ <: CharSequence] = sArray
//> arraysAreInvariantInScala : Array[_ <: CharSequence] = Array(a)
val arraysAreCovariantOnJVM = sArray.isInstanceOf[Array[CharSequence]]
//> arraysAreCovariantOnJVM : Boolean = true
Type casting : variable.asInstanceOf[Type]
Met patroonovereenkomst :
variable match {
case _: Type => true
}
Voorbeelden:
val x = 3 //> x : Int = 3
x match {
case _: Int => true//better: do something
case _ => false
} //> res0: Boolean = true
x match {
case _: java.lang.Integer => true//better: do something
case _ => false
} //> res1: Boolean = true
x.isInstanceOf[Int] //> res2: Boolean = true
//x.isInstanceOf[java.lang.Integer]//fruitless type test: a value of type Int cannot also be a Integer
trait Valuable { def value: Int}
case class V(val value: Int) extends Valuable
val y: Valuable = V(3) //> y : Valuable = V(3)
y.isInstanceOf[V] //> res3: Boolean = true
y.asInstanceOf[V] //> res4: V = V(3)
Opmerking: dit gaat alleen over het gedrag op de JVM, op andere platforms (JS, native) type casten / controleren kan zich anders gedragen.
constructors
Primaire constructeur
In Scala is de primaire constructor het lichaam van de klas. De klassenaam wordt gevolgd door een parameterlijst, de constructorargumenten. (Zoals bij elke functie, kan een lege parameterlijst worden weggelaten.)
class Foo(x: Int, y: String) {
val xy: String = y * x
/* now xy is a public member of the class */
}
class Bar {
...
}
De constructieparameters van een instantie zijn niet toegankelijk buiten de hoofdtekst van de instantie, tenzij gemarkeerd als een instantie-lid met het trefwoord val
:
class Baz(val z: String)
// Baz has no other members or methods, so the body may be omitted
val foo = new Foo(4, "ab")
val baz = new Baz("I am a baz")
foo.x // will not compile: x is not a member of Foo
foo.xy // returns "abababab": xy is a member of Foo
baz.z // returns "I am a baz": z is a member of Baz
val bar0 = new Bar
val bar1 = new Bar() // Constructor parentheses are optional here
Alle bewerkingen die moeten worden uitgevoerd wanneer een instantie van een object wordt geïnstantieerd, worden rechtstreeks in de hoofdtekst van de klasse geschreven:
class DatabaseConnection
(host: String, port: Int, username: String, password: String) {
/* first connect to the DB, or throw an exception */
private val driver = new AwesomeDB.Driver()
driver.connect(host, port, username, password)
def isConnected: Boolean = driver.isConnected
...
}
Merk op dat het als een goede praktijk wordt beschouwd om zo min mogelijk bijwerkingen in de constructor te brengen; in plaats van de bovenstaande code, zou men moeten overwegen connect
en disconnect
, zodat de consumentencode verantwoordelijk is voor het plannen van IO.
Hulpconstructeurs
Een klasse kan extra constructors hebben die 'hulpconstructers' worden genoemd. Deze worden gedefinieerd door constructeursdefinities in de vorm def this(...) = e
, waarbij e
een andere constructor moet aanroepen:
class Person(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
// usage:
new Person("Grace Hopper").fullName // returns Grace Hopper
new Person("Grace", "Hopper").fullName // returns Grace Hopper
Dit houdt in dat elke constructor een andere modificator kan hebben: slechts enkele zijn openbaar beschikbaar:
class Person private(val fullName: String) {
def this(firstName: String, lastName: String) = this(s"$firstName $lastName")
}
new Person("Ada Lovelace") // won't compile
new Person("Ada", "Lovelace") // compiles
Op deze manier kunt u bepalen hoe consumentencode de klasse kan instantiëren.