Scala Language
Var, Val i Def
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”
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ń.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łobyjava.lang.NullPointerException
. Używająclazy
, 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'