Buscar..


Sintaxis

  • 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 diferente: new MyClassWithGenericParameters[Double, Any](2.3, 4, 5)
  • class MyClassWithProtectedConstructor protected[my.pack.age](s: String)

Instancia de instancias de clase

Una clase en Scala es un "plano" de una instancia de clase. Una instancia contiene el estado y el comportamiento definidos por esa clase. Para declarar una clase:

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

Se puede crear una instancia de una instancia usando una new palabra clave:

var instance = new MyClass()

o:

var instance = new MyClass

Los paréntesis son opcionales en Scala para crear objetos de una clase que tiene un constructor sin argumentos. Si un constructor de clase toma argumentos:

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

Aquí MyClass requiere un argumento Int , que solo puede usarse internamente para la clase. arg no se puede acceder fuera de MyClass menos que se declare como un campo:

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

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

Alternativamente se puede declarar público en el constructor:

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

Clase de instanciación sin parámetro: {} vs ()

Digamos que tenemos una clase MyClass sin argumento de constructor:

class MyClass

En Scala podemos crear una instancia usando la siguiente sintaxis:

val obj = new MyClass()

O simplemente podemos escribir:

val obj = new MyClass

Pero, si no se presta atención, en algunos casos, el paréntesis opcional puede producir algún comportamiento inesperado. Supongamos que queremos crear una tarea que debería ejecutarse en un hilo separado. A continuación se muestra el código de ejemplo:

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

newThread.start   // prints no output

Podemos pensar que este código de ejemplo, si se ejecuta, imprimirá la Performing task. , pero para nuestra sorpresa, no imprimirá nada. Veamos que está pasando aquí. Si observa detenidamente, hemos utilizado llaves {} , justo después del new Thread . Creó una clase anónima que extiende Thread :

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

Y luego, en el cuerpo de esta clase anónima, definimos nuestra tarea (nuevamente creando una clase Runnable implementa la interfaz Runnable ). Así que podríamos haber pensado que usamos el constructor public Thread(Runnable target) pero de hecho (al ignorar opcional () ) usamos el constructor public Thread() sin nada definido en el cuerpo del método run() . Para corregir el problema, necesitamos usar paréntesis en lugar de llaves.

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

En otras palabras, aquí {} y () no son intercambiables .

Objetos singleton y acompañantes

Objetos Singleton

Scala admite miembros estáticos, pero no de la misma manera que Java. Scala proporciona una alternativa a esto llamada Objetos Singleton . Los objetos Singleton son similares a una clase normal, excepto que no se pueden crear instancias con la new palabra clave. A continuación se muestra una muestra de clase singleton:

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

Tenga en cuenta que hemos utilizado la palabra clave object para definir el objeto singleton (en lugar de 'class' o 'trait'). Como los objetos singleton no pueden ser instanciados, no pueden tener parámetros. El acceso a un objeto singleton se ve así:

Factorial.getCache() //returns the cache

Tenga en cuenta que esto se ve exactamente como acceder a un método estático en una clase de Java.

Objetos Acompañantes

En Scala, los objetos singleton pueden compartir el nombre de una clase correspondiente. En tal escenario, el objeto singleton se conoce como un objeto complementario . Por ejemplo, debajo de la clase Factorial se define, y un objeto complementario (también llamado Factorial ) se define debajo de ella. Por convención, los objetos complementarios se definen en el mismo archivo que su clase complementaria.

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

En este ejemplo, estamos usando un cache privado para almacenar factorial de un número para ahorrar tiempo de cálculo para números repetidos.

Aquí el object Factorial es un objeto compañero y la class Factorial es su clase compañera correspondiente. Los objetos y clases complementarios pueden acceder a private miembros private cada uno. En el ejemplo anterior, la clase Factorial está accediendo al miembro de cache privado de su objeto complementario.

Tenga en cuenta que una nueva instanciación de la clase seguirá utilizando el mismo objeto complementario, por lo que cualquier modificación de las variables miembro de ese objeto se transferirá.

Objetos

Mientras que las clases son más como planos, los objetos son estáticos (es decir, ya están instanciados):

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

Dog.bark() // yields "Raf"

A menudo se usan como un compañero de una clase, le permiten escribir:

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

Comprobación del tipo de instancia

Verificación de tipo : variable.isInstanceOf[Type]

Con coincidencia de patrones (no tan útil en esta forma):

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

Tanto isInstanceOf como la coincidencia de patrones están verificando solo el tipo de objeto, no su parámetro genérico (sin reificación de tipo), excepto los arreglos:

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

Pero

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

Tipo casting : variable.asInstanceOf[Type]

Con la coincidencia de patrones :

variable match {
  case _: Type => true
}

Ejemplos:

  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)

Observación: esto es solo sobre el comportamiento en la JVM, en otras plataformas (JS, nativo) el tipo de conversión / comprobación podría comportarse de manera diferente.

Constructores

Constructor primario

En Scala el constructor primario es el cuerpo de la clase. El nombre de la clase va seguido de una lista de parámetros, que son los argumentos del constructor. (Como con cualquier función, se puede omitir una lista de parámetros vacía).

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

class Bar {
    ...
}

Los parámetros de construcción de una instancia no son accesibles fuera de su cuerpo de constructor a menos que estén marcados como miembros de instancia por la palabra clave 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

Cualquier operación que deba realizarse cuando se crea una instancia de un objeto se escribe directamente en el cuerpo de la clase:

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

Tenga en cuenta que se considera una buena práctica poner la menor cantidad posible de efectos secundarios en el constructor; en lugar del código anterior, se debe considerar tener métodos de connect y disconnect para que el código del consumidor sea responsable de programar la IO.

Constructores Auxiliares

Una clase puede tener constructores adicionales llamados 'constructores auxiliares'. Estas están definidas por definiciones de constructor en la forma def this(...) = e , donde e debe invocar a otro constructor:

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

Esto implica que cada constructor puede tener un modificador diferente: solo algunos pueden estar disponibles públicamente:

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

De esta manera puede controlar cómo el código de consumidor puede instanciar la clase.



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow