Scala Language
Classi e oggetti
Ricerca…
Sintassi
-
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)
o con un tipo diverso:new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
-
class MyClassWithProtectedConstructor protected[my.pack.age](s: String)
Istanziare istanze di classe
Una classe in Scala è un "progetto" di un'istanza di classe. Un'istanza contiene lo stato e il comportamento definiti da tale classe. Per dichiarare una classe:
class MyClass{} // curly braces are optional here as class body is empty
Un'istanza può essere istanziata usando una new
parola chiave:
var instance = new MyClass()
o:
var instance = new MyClass
Le parentesi sono facoltative in Scala per la creazione di oggetti da una classe che ha un costruttore senza argomenti. Se un costruttore di classi accetta argomenti:
class MyClass(arg : Int) // Class definition
var instance = new MyClass(2) // Instance instantiation
instance.arg // not allowed
Qui MyClass
richiede un argomento Int
, che può essere usato solo internamente alla classe. non è possibile accedere ad arg
al di fuori di MyClass
meno che non sia dichiarato come campo:
class MyClass(arg : Int){
val prop = arg // Class field declaration
}
var obj = new MyClass(2)
obj.prop // legal statement
In alternativa, può essere dichiarato pubblico nel costruttore:
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
Classe di istanziazione senza parametro: {} vs ()
Diciamo che abbiamo una classe MyClass senza argomenti del costruttore:
class MyClass
In Scala possiamo istanziarlo usando la seguente sintassi:
val obj = new MyClass()
O possiamo semplicemente scrivere:
val obj = new MyClass
Ma, se non prestata attenzione, in alcuni casi la parentesi opzionale potrebbe produrre un comportamento inaspettato. Supponiamo di voler creare un'attività che dovrebbe essere eseguita in un thread separato. Di seguito è riportato il codice di esempio:
val newThread = new Thread { new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
}
newThread.start // prints no output
Potremmo pensare che questo codice di esempio, se eseguito, stamperà Performing task.
, ma con nostra sorpresa, non stamperà nulla. Vediamo cosa sta succedendo qui. Se guardi più da vicino, abbiamo usato le parentesi graffe {}
, subito dopo la new Thread
. Ha creato una classe anonima che estende Thread
:
val newThread = new Thread {
//creating anonymous class extending Thread
}
E poi nel corpo di questa classe anonima, abbiamo definito il nostro compito (ancora una volta creando una classe anonima che implementa l'interfaccia Runnable
). Quindi potremmo aver pensato che abbiamo usato il costruttore public Thread(Runnable target)
ma di fatto (ignorando facoltativo ()
) abbiamo usato public Thread()
costruttore public Thread()
con niente definito nel metodo body of run()
. Per correggere il problema, dobbiamo utilizzare le parentesi anziché le parentesi graffe.
val newThread = new Thread ( new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
)
In altre parole, qui {}
e ()
non sono intercambiabili .
Singleton & Companion Objects
Oggetti Singleton
Scala supporta membri statici, ma non nello stesso modo di Java. Scala fornisce un'alternativa a questo chiamato Singleton Objects . Gli oggetti Singleton sono simili a una classe normale, tranne per il fatto che non possono essere istanziati utilizzando la new
parola chiave. Di seguito è riportata una classe di esempio singleton:
object Factorial {
private val cache = Map[Int, Int]()
def getCache = cache
}
Si noti che abbiamo usato la parola chiave object
per definire l'oggetto singleton (invece di 'class' o 'trait'). Poiché gli oggetti singleton non possono essere istanziati, non possono avere parametri. L'accesso a un oggetto singleton ha il seguente aspetto:
Factorial.getCache() //returns the cache
Si noti che questo sembra esattamente come accedere a un metodo statico in una classe Java.
Companion Objects
In oggetti Singleton Scala può condividere il nome di una classe corrispondente. In uno scenario di questo tipo, l'oggetto singleton viene definito come un oggetto companion . Ad esempio, sotto la classe Factorial
è definito, e sotto di esso è definito un oggetto companion (chiamato anche Factorial
). Per convenzione gli oggetti companion sono definiti nello stesso file della loro classe companion.
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 questo esempio utilizziamo una cache
privata per memorizzare fattoriale di un numero per risparmiare tempo di calcolo per numeri ripetuti.
Qui object Factorial
è un oggetto compagno e class Factorial
è la corrispondente classe compagno. Gli oggetti e le classi Companion possono accedere ai rispettivi membri private
. Nell'esempio sopra la classe Factorial
sta accedendo al membro della cache
privata dell'oggetto associato.
Si noti che una nuova istanziazione della classe continuerà a utilizzare lo stesso oggetto compagno, quindi qualsiasi modifica alle variabili membro di tale oggetto verrà trasferita.
Oggetti
Mentre le classi sono più simili a progetti, gli oggetti sono statici (cioè già istanziati):
object Dog {
def bark: String = "Raf"
}
Dog.bark() // yields "Raf"
Sono spesso usati come compagni di una classe, ti permettono di scrivere:
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
Controllo del tipo di istanza
Verifica del tipo : variable.isInstanceOf[Type]
Con la corrispondenza del modello (non così utile in questa forma):
variable match {
case _: Type => true
case _ => false
}
Sia isInstanceOf
che pattern matching stanno controllando solo il tipo dell'oggetto, non il suo parametro generico (nessuna reificazione del tipo), ad eccezione degli array:
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
Ma
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
Digitare casting : variable.asInstanceOf[Type]
Con la corrispondenza del modello :
variable match {
case _: Type => true
}
Esempi:
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)
Nota: si tratta solo del comportamento sulla JVM, su altre piattaforme (JS, native) il tipo di casting / controllo potrebbe comportarsi diversamente.
Costruttori
Costruttore primario
In Scala il costruttore principale è il corpo della classe. Il nome della classe è seguito da un elenco di parametri, che sono gli argomenti del costruttore. (Come con qualsiasi funzione, un elenco di parametri vuoto può essere omesso.)
class Foo(x: Int, y: String) {
val xy: String = y * x
/* now xy is a public member of the class */
}
class Bar {
...
}
I parametri di costruzione di un'istanza non sono accessibili al di fuori del suo corpo del costruttore a meno che non siano contrassegnati come membro di istanza dalla parola chiave 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
Qualsiasi operazione da eseguire quando un'istanza di un oggetto viene creata un'istanza viene scritta direttamente nel corpo della classe:
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
...
}
Si noti che è considerata una buona pratica inserire il minor numero possibile di effetti collaterali nel costruttore; al posto del codice precedente, si dovrebbe considerare di connect
e disconnect
metodi in modo che il codice del consumatore sia responsabile della pianificazione dell'IO.
Costruttori ausiliari
Una classe può avere costruttori aggiuntivi chiamati "costruttori ausiliari". Questi sono definiti dalle definizioni del costruttore nel formato def this(...) = e
, dove e
deve richiamare un altro costruttore:
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
Ciò implica che ogni costruttore può avere un modificatore diverso: solo alcuni possono essere disponibili pubblicamente:
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
In questo modo puoi controllare in che modo il codice del consumatore può istanziare la classe.