Поиск…


Синтаксис

  • 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) или с другим типом: new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
  • class MyClassWithProtectedConstructor protected[my.pack.age](s: String)

Выполнить экземпляр класса

Класс в Scala является «планом» экземпляра класса. Экземпляр содержит состояние и поведение, определенные этим классом. Чтобы объявить класс:

class MyClass{}  // curly braces are optional here as class body is empty

Экземпляр может быть создан с использованием new ключевого слова:

var instance = new MyClass()

или же:

var instance = new MyClass

Круглые скобки необязательны в Scala для создания объектов из класса с конструктором без аргументов. Если конструктор класса принимает аргументы:

class MyClass(arg : Int)       // Class definition
var instance = new MyClass(2)  // Instance instantiation
instance.arg                   // not allowed

Здесь MyClass требует один аргумент Int , который может использоваться только внутри класса. arg не может быть доступен за пределами MyClass если он не объявлен как поле:

class MyClass(arg : Int){ 
    val prop = arg  // Class field declaration
} 

var obj = new MyClass(2)
obj.prop     // legal statement

В качестве альтернативы он может быть объявлен публичным в конструкторе:

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

Создание экземпляра класса без параметров: {} vs ()

Допустим, у нас есть класс MyClass без аргумента конструктора:

class MyClass

В Scala мы можем создать экземпляр, используя синтаксис ниже:

val obj = new MyClass()

Или мы можем просто написать:

val obj = new MyClass

Но, если не обратить внимание, в некоторых случаях необязательная скобка может привести к неожиданному поведению. Предположим, мы хотим создать задачу, которая должна выполняться в отдельном потоке. Ниже приведен пример кода:

val newThread = new Thread { new Runnable {
        override def run(): Unit = {
            // perform task
            println("Performing task.")
        }
      }
    }

newThread.start   // prints no output

Мы можем подумать, что этот примерный код, если он будет выполнен, будет печатать исполняемую Performing task. , но, к нашему удивлению, он ничего не напечатает. Давайте посмотрим, что здесь происходит. Если вы посмотрите ближе, мы использовали фигурные скобки {} , сразу после new Thread . Он создал анонимный класс, который расширяет Thread :

val newThread = new Thread {
  //creating anonymous class extending Thread
}

А затем в теле этого анонимного класса мы определили нашу задачу (снова создавая аннонимный класс, реализующий интерфейс Runnable ). Таким образом, мы могли подумать, что мы использовали public Thread(Runnable target) но на самом деле (игнорируя необязательный () ), мы использовали public Thread() не имеющий ничего определенного в теле метода run() . Чтобы исправить проблему, нам нужно использовать скобки вместо фигурных скобок.

val newThread = new Thread ( new Runnable {
        override def run(): Unit = {
            // perform task
            println("Performing task.")
        }
      }
    )

Другими словами, здесь {} и () не являются взаимозаменяемыми .

Объекты Singleton & Companion

Одиночные объекты

Scala поддерживает статические члены, но не так же, как Java. Scala предоставляет альтернативу этому объекту Singleton . Объекты Singleton аналогичны нормальному классу, за исключением того, что они не могут быть созданы с использованием new ключевого слова. Ниже приведен пример одноэлементного класса:

object Factorial {
    private val cache = Map[Int, Int]()
    def getCache = cache
}

Обратите внимание, что мы использовали ключевое слово object для определения объекта singleton (вместо «class» или «trait»). Поскольку объекты singleton не могут быть созданы, они не могут иметь параметры. Доступ к объекту singleton выглядит следующим образом:

Factorial.getCache() //returns the cache

Обратите внимание, что это похоже на доступ к статическому методу в классе Java.

Сопутствующие объекты

В объектах Scala Singleton можно использовать имя соответствующего класса. В таком сценарии одноэлементный объект упоминается как объект- компаньон . Например, под классом Factorial определено, и под ним определяется объект-компаньон (также называемый Factorial ). По соглашению сопутствующие объекты определяются в том же файле, что и их класс-компаньон.

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

В этом примере мы используем закрытый cache для хранения факториала числа, чтобы сэкономить время вычисления для повторных номеров.

Здесь object Factorial является сопутствующим объектом, а class Factorial - соответствующим классом компаньона. Сопутствующие объекты и классы могут обращаться к private членам друг друга. В приведенном выше примере Factorial class обращается к частному cache своего компаньона.

Обратите внимание, что новый экземпляр класса будет по-прежнему использовать один и тот же объект-компаньон, поэтому любая модификация переменных-членов этого объекта будет перенесена.

Объекты

Если классы больше похожи на чертежи, объекты статичны (т.е. уже созданы):

object Dog {
    def bark: String = "Raf"
}

Dog.bark() // yields "Raf"

Они часто используются в качестве компаньона для класса, они позволяют вам писать:

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

Проверка типа экземпляра

Проверка типа : variable.isInstanceOf[Type]

При сопоставлении с образцом (не так полезно в этой форме):

variable match {
  case _: Type => true
  case _ => false
}

Оба isInstanceOf и сопоставление образцов проверяют только тип объекта, а не его общий параметр (без переопределения типа), за исключением массивов:

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

Но

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

Тип литья : variable.asInstanceOf[Type]

С учетом соответствия шаблону :

variable match {
  case _: Type => true
}

Примеры:

  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)

Примечание. Это касается только поведения на JVM, на других платформах (JS, native) тип casting / check может вести себя по-разному.

Конструкторы

Первичный конструктор

В Scala основной конструктор является телом класса. За именем класса следует список параметров, которые являются аргументами конструктора. (Как и в любой функции, пустой список параметров может быть опущен.)

class Foo(x: Int, y: String) {
    val xy: String = y * x
    /* now xy is a public member of the class */
}

class Bar {
    ...
}

Параметры конструкции экземпляра недоступны вне его тела конструктора, если они не отмечены как член экземпляра ключевым словом 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

Любые операции, которые должны выполняться при создании экземпляра объекта, записываются непосредственно в тело класса:

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
    ...
}

Обратите внимание, что считается хорошей практикой максимально возможное количество побочных эффектов в конструкторе; вместо вышеуказанного кода следует рассмотреть возможность connect и disconnect методов, чтобы потребительский код отвечал за планирование IO.

Вспомогательные конструкторы

Класс может иметь дополнительные конструкторы, называемые «вспомогательными конструкторами». Они определяются определениями конструктора в форме def this(...) = e , где e должен вызывать другой конструктор:

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

Это означает, что каждый конструктор может иметь другой модификатор: только некоторые из них могут быть доступны публично:

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

Таким образом, вы можете контролировать, как потребительский код может создавать экземпляр класса.



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow