Scala Language
Klasy i przedmioty
Szukaj…
Składnia
-
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)
lub innego typu:new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
-
class MyClassWithProtectedConstructor protected[my.pack.age](s: String)
Instancje wystąpień klas
Klasa w Scali jest „planem” instancji klasy. Instancja zawiera stan i zachowanie zdefiniowane przez tę klasę. Aby zadeklarować klasę:
class MyClass{} // curly braces are optional here as class body is empty
Instancję można utworzyć za pomocą new
słowa kluczowego:
var instance = new MyClass()
lub:
var instance = new MyClass
Nawiasy są opcjonalne w Scali do tworzenia obiektów z klasy, która ma konstruktor bez argumentów. Jeśli konstruktor klasy przyjmuje argumenty:
class MyClass(arg : Int) // Class definition
var instance = new MyClass(2) // Instance instantiation
instance.arg // not allowed
Tutaj MyClass
wymaga jednego argumentu Int
, którego można użyć tylko wewnętrznie w klasie. arg
nie może być dostępny poza MyClass
chyba że zostanie zadeklarowany jako pole:
class MyClass(arg : Int){
val prop = arg // Class field declaration
}
var obj = new MyClass(2)
obj.prop // legal statement
Alternatywnie może być zadeklarowany publicznie w konstruktorze:
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
Tworzenie instancji klasy bez parametru: {} vs ()
Załóżmy, że mamy klasę MyClass bez argumentu konstruktora:
class MyClass
W Scali możemy utworzyć go za pomocą poniższej składni:
val obj = new MyClass()
Lub możemy po prostu napisać:
val obj = new MyClass
Jeśli jednak nie zwróci się na to uwagi, w niektórych przypadkach opcjonalny nawias może spowodować nieoczekiwane zachowanie. Załóżmy, że chcemy utworzyć zadanie, które powinno działać w osobnym wątku. Poniżej znajduje się przykładowy kod:
val newThread = new Thread { new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
}
newThread.start // prints no output
Możemy myśleć, że ten przykładowy kod, jeśli zostanie wykonany, wydrukuje Performing task.
, ale ku naszemu zaskoczeniu nic nie wydrukuje. Zobaczmy, co się tutaj dzieje. Jeśli przyjrzysz się bliżej, użyliśmy nawiasów klamrowych {}
, zaraz po new Thread
. Stworzył anonimową klasę, która rozszerza Thread
:
val newThread = new Thread {
//creating anonymous class extending Thread
}
A następnie w treści tej anonimowej klasy zdefiniowaliśmy nasze zadanie (ponownie tworząc anonimową klasę implementującą interfejs Runnable
). Mogliśmy więc pomyśleć, że użyliśmy public Thread(Runnable target)
ale w rzeczywistości (ignorując opcjonalne ()
) użyliśmy public Thread()
konstruktora public Thread()
którym nic nie zdefiniowano w treści metody run()
. Aby rozwiązać problem, musimy użyć nawiasów zamiast nawiasów klamrowych.
val newThread = new Thread ( new Runnable {
override def run(): Unit = {
// perform task
println("Performing task.")
}
}
)
Innymi słowy, tutaj {}
i ()
nie są wymienne .
Obiekty Singleton i Companion
Obiekty Singleton
Scala obsługuje elementy statyczne, ale nie w taki sam sposób jak Java. Scala stanowi alternatywę dla tego obiektu o nazwie Singleton Objects . Obiekty Singleton są podobne do normalnej klasy, z tym wyjątkiem, że nie można ich tworzyć za pomocą new
słowa kluczowego. Poniżej znajduje się przykładowa klasa singletona:
object Factorial {
private val cache = Map[Int, Int]()
def getCache = cache
}
Zauważ, że użyliśmy słowa kluczowego object
do zdefiniowania obiektu singleton (zamiast „class” lub „trait”). Ponieważ obiekty singletonowe nie mogą być tworzone, nie mogą mieć parametrów. Dostęp do obiektu singleton wygląda następująco:
Factorial.getCache() //returns the cache
Zauważ, że wygląda to dokładnie tak, jak dostęp do metody statycznej w klasie Java.
Obiekty towarzyszące
W Scali obiekty singletonowe mogą dzielić nazwę odpowiedniej klasy. W takim scenariuszu obiekt singleton jest nazywany obiektem towarzyszącym . Na przykład poniżej klasy Zdefiniowano czynnik Factorial
, a pod nim zdefiniowano obiekt towarzyszący (również o nazwie czynnik Factorial
). Zgodnie z konwencją obiekty towarzyszące są zdefiniowane w tym samym pliku, co ich klasa towarzysząca.
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
W tym przykładzie używamy prywatnej cache
do przechowywania silni liczby w celu zaoszczędzenia czasu obliczania powtarzanych liczb.
W tym przypadku object Factorial
jest obiektem towarzyszącym, a class Factorial
jest odpowiednią klasą towarzyszącą. Obiekty i klasy towarzyszące mogą uzyskiwać dostęp do swoich private
członków. W powyższym przykładzie klasa Factorial
uzyskuje dostęp do prywatnego elementu cache
swojego obiektu towarzyszącego.
Zauważ, że nowa instancja klasy będzie nadal wykorzystywać ten sam obiekt towarzyszący, więc wszelkie modyfikacje zmiennych składowych tego obiektu zostaną przeniesione.
Obiekty
Podczas gdy Klasy są bardziej jak schematy, Obiekty są statyczne (tj. Już utworzone):
object Dog {
def bark: String = "Raf"
}
Dog.bark() // yields "Raf"
Często są używane jako towarzysz klasy, pozwalają pisać:
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
Sprawdzanie typu wystąpienia
Sprawdzanie typu : variable.isInstanceOf[Type]
Z dopasowaniem wzorca (niezbyt przydatne w tym formularzu):
variable match {
case _: Type => true
case _ => false
}
Zarówno isInstanceOf
jak i dopasowanie wzorca sprawdzają tylko typ obiektu, a nie jego ogólny parametr (bez zmiany typu), z wyjątkiem tablic:
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
Ale
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
Typ rzutowania : variable.asInstanceOf[Type]
variable match {
case _: Type => true
}
Przykłady:
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)
Uwaga: Chodzi tylko o zachowanie w JVM, na innych platformach (JS, natywny) rzutowanie / sprawdzanie typu może zachowywać się inaczej.
Konstruktory
Główny Konstruktor
W Scali głównym konstruktorem jest ciało klasy. Po nazwie klasy następuje lista parametrów, które są argumentami konstruktora. (Jak w przypadku każdej funkcji, pusta lista parametrów może zostać pominięta.)
class Foo(x: Int, y: String) {
val xy: String = y * x
/* now xy is a public member of the class */
}
class Bar {
...
}
Parametry konstrukcyjne instancji nie są dostępne poza jej konstruktorem, chyba że zostaną oznaczone jako element instancji słowem kluczowym 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
Wszelkie operacje, które należy wykonać, gdy wystąpi instancja obiektu, są zapisywane bezpośrednio w treści klasy:
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
...
}
Zauważ, że dobrą praktyką jest wprowadzanie do konstruktora jak najmniejszej liczby skutków ubocznych; zamiast powyższego kodu należy rozważyć zastosowanie metod connect
i disconnect
, aby kod konsumenta był odpowiedzialny za planowanie operacji we / wy.
Konstruktorzy pomocniczy
Klasa może mieć dodatkowe konstruktory zwane „konstruktorami pomocniczymi”. Są one zdefiniowane przez definicje konstruktorów w postaci def this(...) = e
, gdzie e
musi wywoływać innego konstruktora:
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
Oznacza to, że każdy konstruktor może mieć inny modyfikator: tylko niektóre mogą być dostępne publicznie:
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
W ten sposób możesz kontrolować, w jaki sposób kod konsumenta może utworzyć instancję klasy.