Zoeken…


Opmerkingen

Omdat val semantisch statisch is, worden ze 'ter plekke' geïnitialiseerd, waar ze ook in de code voorkomen. Dit kan verrassend en ongewenst gedrag veroorzaken wanneer het wordt gebruikt in abstracte klassen en eigenschappen.

Laten we bijvoorbeeld zeggen dat we een eigenschap PlusOne die een incrementele bewerking op een ingepakte Int definieert. Aangezien Int 's onveranderlijk zijn, is de waarde plus één bekend bij initialisatie en zal deze daarna nooit meer worden gewijzigd, dus semantisch is het een val . Als u het op deze manier definieert, krijgt u echter een onverwacht resultaat.

trait PlusOne {
    val i:Int

    val incr = i + 1
}

class IntWrapper(val i: Int) extends PlusOne

Het maakt niet uit welke waarde i u bouwen IntWrapper met, bellen .incr op de geretourneerde object zal altijd terugkeren 1. Dit is omdat de val incr wordt geïnitialiseerd in de eigenschap, voor de uitbreiding van de klas, en in die tijd i alleen de standaardwaarde 0 . (In andere omstandigheden kan het worden gevuld met Nil , null of een vergelijkbare standaard.)

De algemene regel is dus om te voorkomen dat val voor elke waarde die afhankelijk is van een abstract veld. Gebruik in plaats daarvan lazy val , die niet evalueert totdat het nodig is, of def , die evalueert telkens wanneer het wordt aangeroepen. Merk echter op dat als de lazy val wordt gedwongen te evalueren met een val voordat de initialisatie is voltooid, dezelfde fout optreedt.

Een viool (geschreven in Scala-Js, maar hetzelfde gedrag is van toepassing) kan hier worden gevonden .

Var, Val en Def

var

Een var is een referentievariabele, vergelijkbaar met variabelen in talen zoals Java. Verschillende objecten kunnen vrij worden toegewezen aan een var , zolang het gegeven object hetzelfde type heeft waarmee de var is gedeclareerd met:

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

Merk op dat in het bovenstaande voorbeeld het type var werd afgeleid door de compiler gezien de eerste waardetoekenning.

val

Een val is een constante referentie. Aldus een nieuw object kan niet worden toegewezen aan een val die reeds toegewezen.

scala> val y = 1
y: Int = 1

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

Het object waarnaar een val verwijst, is echter niet constant. Dat object kan worden gewijzigd:

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

Een def definieert een werkwijze. Een methode kan niet opnieuw worden toegewezen.

scala> def z = 1
z: Int

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

In de bovenstaande voorbeelden retourneren val y en def z dezelfde waarde. Een def wordt echter geëvalueerd wanneer deze wordt aangeroepen , terwijl een val of var wordt geëvalueerd wanneer deze wordt toegewezen . Dit kan leiden tot ander gedrag wanneer de definitie bijwerkingen heeft:

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

functies

Omdat functies waarden zijn, kunnen ze worden toegewezen aan val / var / def s. Al het andere werkt op dezelfde manier als hierboven:

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

Luie val

lazy val is een taalfunctie waarbij de initialisatie van een val wordt uitgesteld totdat deze voor de eerste keer wordt gebruikt. Na dat punt werkt het net als een normale val .

Om het te gebruiken voeg je het lazy trefwoord toe voor val . Bijvoorbeeld met behulp van de 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

Dit voorbeeld toont de uitvoeringsvolgorde. Wanneer de lazy val wordt gedeclareerd, is het enige dat wordt opgeslagen naar de foo waarde een luie functieaanroep die nog niet is geëvalueerd. Wanneer de reguliere val is ingesteld, zien we de println oproep uit te voeren en de waarde wordt toegewezen aan bar . Wanneer we foo de eerste keer evalueren, zien we println uitvoeren - maar niet wanneer het de tweede keer wordt geëvalueerd. Op dezelfde manier zien we dat wanneer bar wordt geëvalueerd, println uitgevoerd - alleen wanneer het wordt gedeclareerd.

Wanneer 'lui' gebruiken

  1. Initialisatie is rekenkundig duur en het gebruik van val is zeldzaam.

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

    tiresomeValue kost veel tijd om te berekenen en wordt niet altijd gebruikt. Als u er een lazy val bespaart u onnodige berekeningen.

  2. Cyclische afhankelijkheden oplossen

    Laten we een voorbeeld bekijken met twee objecten die tegelijkertijd moeten worden gedeclareerd tijdens het instantiëren:

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

    Zonder het trefwoord lazy kunnen de respectieve objecten geen lid zijn van een object. De uitvoering van een dergelijk programma zou resulteren in een java.lang.NullPointerException . Door lazy , kan de referentie worden toegewezen voordat deze wordt geïnitialiseerd, zonder angst voor een niet-geïnitialiseerde waarde.

Overbelasting Def

U kunt een def overbelasten als de handtekening anders is:

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

Dit werkt hetzelfde, of het nu binnen klassen, eigenschappen, objecten is of niet.

Benoemde parameters

Bij het aanroepen van een def kunnen parameters expliciet op naam worden toegewezen. Dit betekent dat ze niet correct moeten worden geordend. Definieer bijvoorbeeld printUs() als:

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

Nu kan het op deze manieren (onder andere) worden genoemd:

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

Dit resulteert in het afdrukken van one, two, three in alle gevallen.

Als niet alle argumenten worden genoemd, worden de eerste argumenten op volgorde gekoppeld. Geen positioneel (niet-genoemd) argument mag op een genoemd volgen:

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
Licentie onder CC BY-SA 3.0
Niet aangesloten bij Stack Overflow