Scala Language
Klasser och objekt
Sök…
Syntax
-
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)
eller med en annan typ:new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
-
class MyClassWithProtectedConstructor protected[my.pack.age](s: String)
Instantiate Class Instances
En klass i Scala är en "ritning" av en klassinstans. En instans innehåller tillstånd och beteende som definieras av den klassen. Att förklara en klass:
class MyClass{} // curly braces are optional here as class body is empty
En instans kan instanseras med new
nyckelord:
var instance = new MyClass()
eller:
var instance = new MyClass
Parenteser är valfria i Scala för att skapa objekt från en klass som har en konstruktör utan argument. Om en klasskonstruktör tar argument:
class MyClass(arg : Int) // Class definition
var instance = new MyClass(2) // Instance instantiation
instance.arg // not allowed
Här kräver MyClass
ett Int
argument, som endast kan användas internt för klassen. arg
kan inte nås utanför MyClass
om det inte deklareras som ett fält:
class MyClass(arg : Int){
val prop = arg // Class field declaration
}
var obj = new MyClass(2)
obj.prop // legal statement
Alternativt kan det förklaras offentligt i konstruktören:
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
Instantiating class utan parameter: {} vs ()
Låt oss säga att vi har en klass MyClass utan konstruktörargument:
class MyClass
I Scala kan vi instansera det med syntax nedan:
val obj = new MyClass()
Eller så kan vi helt enkelt skriva:
val obj = new MyClass
Men om inte uppmärksamhet kan i vissa fall valfritt parentes producera något oväntat beteende. Anta att vi vill skapa en uppgift som ska köras i en separat tråd. Nedan är provkoden:
val newThread = new Thread { new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
}
newThread.start // prints no output
Vi kanske tror att denna provkod om den körs kommer att skriva ut Performing task.
, men till vår överraskning kommer det inte att skriva ut någonting. Låt oss se vad som händer här. Om du tittar närmare har vi använt lockiga hängslen {}
, direkt efter new Thread
. Det skapade en anonym klass som utökar Thread
:
val newThread = new Thread {
//creating anonymous class extending Thread
}
Och i kroppen av denna anonyma klass definierade vi vår uppgift (återigen att skapa en annonym klass som implementerar Runnable
interface). Så vi kan ha trott att vi använt public Thread(Runnable target)
konstruktören men i själva verket (genom att ignorera tillval ()
) vi använt public Thread()
konstruktör med ingenting definieras i kroppen av run()
metoden. För att åtgärda problemet måste vi använda parenteser istället för lockiga hängslen.
val newThread = new Thread ( new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
)
Med andra ord är här {}
och ()
inte utbytbara .
Singleton & Companion Objects
Singleton-objekt
Scala stöder statiska medlemmar, men inte på samma sätt som Java. Scala ger ett alternativ till detta kallas Singleton Objects . Singleton-objekt liknar en vanlig klass, förutom att de inte kan instanseras med det new
nyckelordet. Nedan är ett exempel på singleton-klassen:
object Factorial {
private val cache = Map[Int, Int]()
def getCache = cache
}
Notera att vi har använt object
nyckelordet för att definiera singleton-objekt (i stället för 'klass' eller 'drag'). Eftersom singleton-objekt inte kan instanseras kan de inte ha parametrar. Att komma åt ett singleton-objekt ser så här ut:
Factorial.getCache() //returns the cache
Observera att det ser exakt ut som att komma åt en statisk metod i en Java-klass.
Companion Objects
I Scala kan singleton föremål dela namnet på motsvarande klass. I ett sådant scenario kallas singletonobjektet som ett följeslagningsobjekt . Till exempel definieras under klassen Factorial
och ett följeslagareobjekt (även benämnt Factorial
) definieras under det. Genom konvention definieras följeslagare i samma fil som deras ledsagarklass.
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
I det här exemplet använder vi en privat cache
att lagra fakultet för ett nummer för att spara beräkningstid för upprepade nummer.
Här är object Factorial
ett följeslagareobjekt och class Factorial
är dess motsvarande ledsagarklass. Companion-objekt och klasser kan komma åt varandras private
medlemmar. I exemplet ovan får Factorial
klassen åtkomst till den privata cache
medlemmen i dess följeslagareobjekt.
Observera att en ny instansering av klassen fortfarande kommer att använda samma följeslagareobjekt, så varje ändring av medlemsvariabler för det objektet kommer att övergå.
Objekt
Medan klasser är mer som ritningar, är föremål statiska (dvs. redan instanserade):
object Dog {
def bark: String = "Raf"
}
Dog.bark() // yields "Raf"
De används ofta som följeslagare till en klass, de låter dig skriva:
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
Kontroll av förekomsttyp
Typkontroll : variable.isInstanceOf[Type]
Med mönstermatchning (inte så användbar i den här formen):
variable match {
case _: Type => true
case _ => false
}
Både isInstanceOf
och matchning av mönster kontrollerar bara objektets typ, inte dess generiska parameter (ingen typreifiering), med undantag för matriser:
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
Men
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
Typgjutning : variable.asInstanceOf[Type]
Med mönster matchning :
variable match {
case _: Type => true
}
Exempel:
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)
Anmärkning: Det här handlar bara om beteendet på JVM, på andra plattformar (JS, inbyggd) typgjutning / -kontroll kan fungera annorlunda.
konstruktörer
Primärkonstruktör
I Scala är den primära konstruktören klassens kropp. Klassnamnet följs av en parameterlista, som är konstruktörens argument. (Som med alla funktioner kan en tom parameterlista utelämnas.)
class Foo(x: Int, y: String) {
val xy: String = y * x
/* now xy is a public member of the class */
}
class Bar {
...
}
Konstruktionsparametrarna för en instans är inte tillgängliga utanför dess konstruktörskropp om de inte är markerade som en instansmedlem med val
nyckelordet:
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
Alla operationer som ska utföras när en instans av ett objekt instanseras skrivs direkt i kroppen:
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
...
}
Observera att det anses vara god praxis att lägga så få biverkningar i konstruktören som möjligt; istället för koden ovan bör man överväga att använda connect
och disconnect
så att konsumentkoden är ansvarig för schemaläggning av IO.
Hjälpkonstruktörer
En klass kan ha ytterligare konstruktörer som kallas ”hjälpkonstruktörer”. Dessa definieras av konstruktordefinitioner i formen def this(...) = e
, där e
måste åberopa en annan konstruktör:
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
Detta innebär att varje konstruktör kan ha en annan modifierare: endast vissa kan vara tillgängliga offentligt:
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
På detta sätt kan du styra hur konsumentkoden kan instansera klassen.