Szukaj…


Uwagi

Ponieważ val są semantycznie statyczne, są inicjowane „na miejscu”, gdziekolwiek występują w kodzie. Może to powodować zaskakujące i niepożądane zachowanie, gdy jest stosowane w abstrakcyjnych klasach i cechach.

Na przykład, powiedzmy, że chcielibyśmy stworzyć cechę o nazwie PlusOne która definiuje operację przyrostową na zawiniętym Int . Ponieważ Int są niezmienne, wartość plus jeden jest znana przy inicjalizacji i nigdy nie będzie później zmieniana, więc semantycznie jest to val . Jednak zdefiniowanie go w ten sposób spowoduje nieoczekiwany rezultat.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

Bez względu na to, jaką wartość i skonstruować IntWrapper z nazywając .incr na zwróconego przedmiotu zawsze zwróci 1. To dlatego, że Val incr jest inicjowany w cechy, zanim klasy rozciągającej, a w tym czasie i ma jedynie wartość domyślną 0 . (W innych warunkach, to może być wypełniona Nil , null lub podobnym domyślnie).

Zatem ogólną zasadą jest unikanie używania val do dowolnej wartości zależnej od pola abstrakcyjnego. Zamiast tego użyj lazy val , który nie ocenia, dopóki nie jest potrzebny, lub def , który ocenia za każdym razem, gdy jest wywoływany. Zauważ jednak, że jeśli lazy val zostanie zmuszona do oszacowania wartości val przed zakończeniem inicjalizacji, wystąpi ten sam błąd.

Skrzypce (napisane w Scala-Js, ale dotyczy to tego samego zachowania) można znaleźć tutaj.

Var, Val i Def

var

var jest zmienną odniesienia, podobną do zmiennych w językach takich jak Java. Różne obiekty mogą być dowolnie przypisywane do var , o ile dany obiekt ma ten sam typ, co var zadeklarowany za pomocą:

scala> var x = 1
x: Int = 1

scala> x = 2
x: Int = 2

scala> x = "foo bar"
<console>:12: error: type mismatch;
 found   : String("foo bar")
 required: Int
       x = "foo bar"
       ^

Uwaga: w powyższym przykładzie typ var został wyprowadzony przez kompilator na podstawie pierwszego przypisania wartości.

val

val jest stałym odniesieniem. Tak więc, nowy obiekt nie może być przypisany do val , który został już przypisany.

scala> val y = 1
y: Int = 1

scala> y = 2
<console>:12: error: reassignment to val
       y = 2
         ^

Jednak obiekt, na który wskazuje val nie jest stały. Ten obiekt może być modyfikowany:

scala> val arr = new Array[Int](2)
arr: Array[Int] = Array(0, 0)

scala> arr(0) = 1

scala> arr
res1: Array[Int] = Array(1, 0)

def

def definiuje metody. Nie można ponownie przypisać metody.

scala> def z = 1
z: Int

scala> z = 2
<console>:12: error: value z_= is not a member of object $iw
       z = 2
       ^

W powyższych przykładach val y i def z zwracają tę samą wartość. Jednak def jest oceniane, gdy jest wywoływane , natomiast val lub var jest oceniane, gdy jest przypisane . Może to powodować różne zachowania, gdy definicja ma skutki uboczne:

scala> val a = {println("Hi"); 1}
Hi
a: Int = 1

scala> def b = {println("Hi"); 1}
b: Int

scala> a + 1
res2: Int = 2

scala> b + 1
Hi
res3: Int = 2

Funkcje

Ponieważ funkcje są wartościami, można je przypisać do val / var / def s. Wszystko inne działa w taki sam sposób jak powyżej:

scala> val x = (x: Int) => s"value=$x"
x: Int => String = <function1>

scala> var y = (x: Int) => s"value=$x"
y: Int => String = <function1>

scala> def z = (x: Int) => s"value=$x"
z: Int => String

scala> x(1)
res0: String = value=1

scala> y(2)
res1: String = value=2

scala> z(3)
res2: String = value=3

Lazy val

lazy val jest funkcją języka, w której inicjalizacja wartości val jest opóźniona do momentu pierwszego dostępu do niej. Po tym punkcie działa jak zwykła val .

Aby go użyć, dodaj lazy słowo kluczowe przed val . Na przykład za pomocą REPL:

scala> lazy val foo = {
     |   println("Initializing")
     |   "my foo value"
     | }
foo: String = <lazy>

scala> val bar = {
     |   println("Initializing bar")
     |   "my bar value"
     | }
Initializing bar
bar: String = my bar value

scala> foo
Initializing
res3: String = my foo value

scala> bar
res4: String = my bar value

scala> foo
res5: String = my foo value

Ten przykład pokazuje kolejność wykonywania. Gdy deklarowana jest wartość lazy val , wszystko, co jest zapisywane w wartości foo to leniwe wywołanie funkcji, które nie zostało jeszcze ocenione. Przy regularnym val jest ustawiona, widzimy println wezwanie wykonać, a wartość jest przypisany do bar . Kiedy ewaluujemy foo po raz pierwszy, widzimy wykonanie println - ale nie, gdy jest oceniane po raz drugi. Podobnie, gdy bar jest oceniany, nie widzimy wykonania println - tylko gdy jest zadeklarowane.

Kiedy używać „leniwego”

  1. Inicjalizacja jest kosztowna obliczeniowo, a użycie val jest rzadkie.

    lazy val tiresomeValue = {(1 to 1000000).filter(x => x % 113 == 0).sum}
    if (scala.util.Random.nextInt > 1000) {
      println(tiresomeValue)
    }
    

    Obliczanie tiresomeValue zajmuje dużo czasu i nie zawsze jest używane. lazy val oszczędza niepotrzebnych obliczeń.

  2. Rozwiązywanie zależności cyklicznych

    Spójrzmy na przykład z dwoma obiektami, które należy zadeklarować jednocześnie podczas tworzenia instancji:

    object comicBook {
      def main(args:Array[String]): Unit = {
        gotham.hero.talk()
        gotham.villain.talk()
      }
    }
    
    class Superhero(val name: String) {
      lazy val toLockUp = gotham.villain
      def talk(): Unit = {
        println(s"I won't let you win ${toLockUp.name}!")
      }
    }
    
    class Supervillain(val name: String) {
      lazy val toKill = gotham.hero
      def talk(): Unit = {
        println(s"Let me loosen up Gotham a little bit ${toKill.name}!")
      }
    }
    
    object gotham {
      val hero: Superhero = new Superhero("Batman")
      val villain: Supervillain = new Supervillain("Joker")
    }
    

    Bez słowa kluczowego lazy odpowiednie obiekty nie mogą być członkami obiektu. Wykonanie takiego programu spowodowałoby java.lang.NullPointerException . Używając lazy , można przypisać referencję przed jej zainicjowaniem, bez obawy o niezainicjowaną wartość.

Overloading Def

Możesz przeładować def jeśli podpis jest inny:

def printValue(x: Int) {
  println(s"My value is an integer equal to $x")
}

def printValue(x: String) {
  println(s"My value is a string equal to '$x'")
}

printValue(1)  // prints "My value is an integer equal to 1"
printValue("1") // prints "My value is a string equal to '1'"

Działa to tak samo, czy wewnątrz klas, cech, obiektów, czy nie.

Nazwane parametry

Podczas wywoływania def parametry mogą być przypisane jawnie według nazwy. Takie postępowanie oznacza, że nie muszą być poprawnie zamawiane. Na przykład zdefiniuj printUs() jako:

// print out the three arguments in order.
def printUs(one: String, two: String, three: String) = 
   println(s"$one, $two, $three")

Teraz można go wywoływać w następujący sposób (między innymi):

printUs("one", "two", "three") 
printUs(one="one", two="two", three="three")
printUs("one", two="two", three="three")
printUs(three="three", one="one", two="two") 

Powoduje to wydrukowanie one, two, three we wszystkich przypadkach.

Jeśli nie wszystkie argumenty są nazwane, pierwsze argumenty są dopasowywane według kolejności. Żaden argument pozycyjny (nienazwany) nie może występować po nazwie:

printUs("one", two="two", three="three") // prints 'one, two, three'
printUs(two="two", three="three", "one") // fails to compile: 'positional after named argument'


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