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]

Z dopasowaniem wzoru :

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.



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow