Sök…


Anmärkningar

Eftersom val är semantiskt statiskt, initialiseras de "på plats" var de än visas i koden. Detta kan ge överraskande och oönskat beteende när det används i abstrakta klasser och drag.

Låt oss till exempel säga att vi skulle vilja göra en egenskap som heter PlusOne som definierar en tilläggsoperation på ett inslaget Int . Eftersom Int är oföränderligt är värdet plus ett känt vid initialisering och kommer aldrig att ändras efteråt, så semantiskt är det en val . Att definiera det på detta sätt kommer dock att ge ett oväntat resultat.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

Oavsett vilket värde i konstruerar IntWrapper med kommer samtal .incr på det returnerade objektet alltid att återvända 1. Detta beror på att val incr initieras i egenskapen , före den utökande klassen, och vid den tiden har i bara standardvärdet för 0 . (Under andra förhållanden kan den vara fylld med Nil , null eller liknande standard.)

Den allmänna regeln är då att undvika att använda val på något värde som beror på ett abstrakt fält. Använd istället lazy val , som inte utvärderar förrän det behövs, eller def , som utvärderar varje gång det kallas. Observera dock att om den lazy val tvingas utvärdera med en val innan initieringen är klar kommer samma fel att inträffa.

En fiol (skriven i Scala-Js, men samma beteende gäller) kan hittas här.

Var, Val och Def

var

En var är en referensvariabel, liknande variabler i språk som Java. Olika objekt kan fritt tilldelas en var , så länge det givna objektet har samma typ som var förklarades med:

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"
       ^

Observera i exemplet ovan att typen av var utleddes av kompilatorn som fick den första värdetilldelningen.

val

En val är en konstant referens. Således kan inte ett nytt objekt tilldelas en val som redan har tilldelats.

scala> val y = 1
y: Int = 1

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

Men objektet som en val pekar på är inte konstant. Detta objekt kan ändras:

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

En def definierar en metod. En metod kan inte tilldelas till.

scala> def z = 1
z: Int

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

I exemplen ovan returnerar val y och def z samma värde. En def utvärderas emellertid när den heter , medan en val eller var utvärderas när den tilldelas . Detta kan resultera i olika beteenden när definitionen har biverkningar:

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

funktioner

Eftersom funktioner är värden kan de tilldelas val / var / def s. Allt annat fungerar på samma sätt som ovan:

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 är en språkfunktion där initialiseringen av en val är försenad tills den nås för första gången. Efter den punkten fungerar det precis som en vanlig val .

För att använda det lägg till det lazy nyckelordet före val . Använd till exempel 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

Detta exempel visar exekveringsorder. När den lazy val deklareras är allt som sparas till foo värdet ett lat funktionssamtal som ännu inte har utvärderats. När den vanliga val är inställd ser vi att det println samtalet körs och värdet tilldelas bar . När vi evalute foo första gången vi ser println köra - men inte när det utvärderas för andra gången. På samma sätt ser vi att utskriften inte println när bar utvärderas - bara när det deklareras.

När man använder "lat"

  1. Initieringen är beräkningskrävande och användningen av val är sällsynt.

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

    tiresomeValue tar lång tid att beräkna, och den används inte alltid. Att göra det till en lazy val sparar onödig beräkning.

  2. Lösa cykliska beroenden

    Låt oss titta på ett exempel med två objekt som måste deklareras samtidigt under inställning:

    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")
    }
    

    Utan nyckelordet lazy kan de respektive objekten inte vara medlemmar i ett objekt. Exekvering av ett sådant program skulle resultera i en java.lang.NullPointerException . Genom att använda lazy kan referensen tilldelas innan den initialiseras, utan rädsla för att ha ett oinitialiserat värde.

Överbelastning Def

Du kan överbelasta en def om signaturen är annorlunda:

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'"

Detta fungerar på samma sätt, oavsett om det finns klasser, drag, objekt eller inte.

Namngivna parametrar

När man åberopar en def kan parametrar tilldelas uttryckligen med namn. Att göra det betyder att de inte behöver ordnas korrekt. Definiera till exempel printUs() som:

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

Nu kan det kallas på dessa sätt (bland annat):

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

Detta resulterar i att one, two, three skrivs ut i alla fall.

Om inte alla argument heter, matchas de första argumenten efter ordning. Inget positionellt (icke namngivet) argument får följa ett namngivet:

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
Licensierat under CC BY-SA 3.0
Inte anslutet till Stack Overflow