Użycie def, val i var w scali

158
class Person(val name:String,var age:Int )
def person = new Person("Kumar",12)
person.age = 20
println(person.age)

Te wiersze kodu wyjściowego 12, mimo że person.age=20zostały pomyślnie wykonane. Okazało się, że dzieje się tak, ponieważ użyłem def w def person = new Person("Kumar",12). Jeśli używam var lub val, wyjście to 20. Rozumiem, że domyślną wartością jest val w scali. To:

def age = 30
age = 45

... wyświetla błąd kompilacji, ponieważ domyślnie jest to wartość val. Dlaczego pierwszy zestaw powyższych wierszy nie działa poprawnie, a jednocześnie nie zawiera błędów?

Byju Veedu
źródło

Odpowiedzi:

254

Istnieją trzy sposoby definiowania rzeczy w Scali:

  • defdefiniuje metodę
  • valdefiniuje stałą wartość (której nie można modyfikować)
  • vardefiniuje zmienną (którą można modyfikować)

Patrząc na twój kod:

def person = new Person("Kumar",12)

To definiuje nową metodę o nazwie person. Możesz wywołać tę metodę tylko bez, ()ponieważ jest zdefiniowana jako metoda bez parametrów. W przypadku metody „blank-paren” można ją wywołać z lub bez „()”. Jeśli po prostu napiszesz:

person

wtedy wywołujesz tę metodę (i jeśli nie przypiszesz wartości zwracanej, zostanie ona po prostu odrzucona). W tym wierszu kodu:

person.age = 20

dzieje się tak, że najpierw wywołujesz personmetodę, a na zwracanej wartości (instancji klasy Person) zmieniasz agezmienną składową.

I ostatnia linijka:

println(person.age)

Tutaj ponownie wywołujesz personmetodę, która zwraca nową instancję klasy Person(z ageustawioną na 12). To jest to samo, co to:

println(person().age)
Jesper
źródło
27
Aby zmylić rzeczy, valmożna zmienić stan wewnętrzny a, ale obiekt, do którego odwołuje się wartość, nie może. A valnie jest stałą.
pferrel
5
Aby jeszcze bardziej zmylić rzeczy, val (i być może także var, nie próbowałem tego) może być użyty do zdefiniowania funkcji. Podczas używania def do zdefiniowania funkcji / metody, treść def jest oceniana za każdym razem, gdy jest wywoływana. Przy używaniu val jest oceniany tylko w punkcie definicji. Zobacz stackoverflow.com/questions/18887264/…
melston
1
@melston Tak, ale metoda i funkcja też nie są dokładnie tym samym .
Jesper
3
aby jeszcze bardziej zmylić rzeczy, def może być również używane do definiowania zmiennych składowych klasy, niekoniecznie do używania var.
Peiti Li
2
@pferrel nie jest zbyt mylące. Tak samo jak w przypadku wersji ostatecznej Java. Możesz oznaczyć Listjako final, ale możesz modyfikować jego zawartość.
jFrenetic,
100

Zacząłbym od rozróżnienia istniejącego w Scali między def , val i var .

  • def - definiuje niezmienną etykietę dla zawartości po prawej stronie, która jest leniwie oceniana - oceniana według nazwy.

  • val - definiuje niezmienną etykietę dla treści po prawej stronie, która jest chętnie / natychmiast oceniana - oceniana według wartości.

  • var - definiuje zmienną modyfikowalną , początkowo ustawioną na wartościowaną zawartość po prawej stronie.

Przykład, pok

scala> def something = 2 + 3 * 4 
something: Int
scala> something  // now it's evaluated, lazily upon usage
res30: Int = 14

Przykład, val

scala> val somethingelse = 2 + 3 * 5 // it's evaluated, eagerly upon definition
somethingelse: Int = 17

Przykład, var

scala> var aVariable = 2 * 3
aVariable: Int = 6

scala> aVariable = 5
aVariable: Int = 5

Zgodnie z powyższym, etykiet z def i val nie można ponownie przypisać, aw przypadku jakiejkolwiek próby podniesiony zostanie błąd podobny do poniższego:

scala> something = 5 * 6
<console>:8: error: value something_= is not a member of object $iw
       something = 5 * 6
       ^

Gdy klasa jest zdefiniowana w następujący sposób:

scala> class Person(val name: String, var age: Int)
defined class Person

a następnie utworzono za pomocą:

scala> def personA = new Person("Tim", 25)
personA: Person

niezmienne etykieta jest tworzony dla tej instancji konkretnej osoby (czyli „Persona”). Za każdym razem, gdy zmienne pole „age” wymaga modyfikacji, taka próba kończy się niepowodzeniem:

scala> personA.age = 44
personA.age: Int = 25

zgodnie z oczekiwaniami „wiek” jest częścią niezmiennej etykiety. Prawidłowy sposób pracy polega na zastosowaniu zmiennej mutowalnej, jak w poniższym przykładzie:

scala> var personB = new Person("Matt", 36)
personB: Person = Person@59cd11fe

scala> personB.age = 44
personB.age: Int = 44    // value re-assigned, as expected

co oczywiste, z odniesienia do zmiennej mutowalnej (tj. „personB”) można zmodyfikować pole mutable class „age”.

Podkreśliłbym jeszcze fakt, że wszystko bierze się z podanej powyżej różnicy, o której każdy programista Scala musi sobie jasno uświadomić.

Paolo Maresca
źródło
Nie sądzę, aby powyższe wyjaśnienie było prawidłowe. Zobacz inne odpowiedzi.
Per Mildner
@PerMildner Czy możesz wyjaśnić, co jest nie tak w powyższej odpowiedzi?
Syed Souban
Nie pamiętam, jaka była moja pierwotna skarga. Jednak ostatnia część odpowiedzi, dotycząca personAet al. wydaje się wyłączony. To, czy modyfikacja ageczłonka działa, czy nie, jest niezależne od tego, czy używasz def personAlub var personB. Różnica polega na tym, że w def personAprzypadku modyfikowania Person-instancji zwróconej z pierwszej oceny personA. Ta instancja jest modyfikowana, ale nie jest zwracana podczas ponownej oceny personA. Zamiast tego, za drugim razem robisz personA.ageto skutecznie new Person("Tim",25).age.
Per Mildner,
29

Z

def person = new Person("Kumar", 12) 

definiujesz funkcję / zmienną leniwą, która zawsze zwraca nową instancję Person o nazwie „Kumar” i wieku 12. Jest to całkowicie poprawne i kompilator nie ma powodu do narzekań. Wywołanie person.age zwróci wiek nowo utworzonej instancji Person, który zawsze wynosi 12.

Podczas pisania

person.age = 45

przypisujesz nową wartość właściwości age w klasie Person, która obowiązuje od momentu zadeklarowania wieku jako var. Kompilator będzie narzekał, jeśli spróbujesz ponownie przypisać personnowy obiekt Person, taki jak

person = new Person("Steve", 13)  // Error
Kintaro
źródło
Tak. Można to łatwo zademonstrować, wywołując metodę hashCode na osobieA
Nilanjan Sarkar
26

Aby zapewnić inną perspektywę, „def” w Scali oznacza coś, co będzie oceniane za każdym razem, gdy zostanie użyte, podczas gdy val jest czymś, co jest oceniane natychmiast i tylko raz . Tutaj wyrażenie def person = new Person("Kumar",12)oznacza, że ​​za każdym razem, gdy użyjemy słowa „osoba”, otrzymamy new Person("Kumar",12)telefon. Dlatego jest naturalne, że te dwie „osoby” nie są ze sobą powiązane.

W ten sposób rozumiem Scalę (prawdopodobnie w bardziej „funkcjonalny” sposób). Nie jestem pewien, czy

def defines a method
val defines a fixed value (which cannot be modified)
var defines a variable (which can be modified)

tak naprawdę ma na myśli Scala. Przynajmniej nie lubię myśleć w ten sposób ...

xji
źródło
20

Jak już mówi Kintaro, osoba jest metodą (z powodu def) i zawsze zwraca nową instancję Person. Jak się przekonałeś, zadziała, jeśli zmienisz metodę na var lub val:

val person = new Person("Kumar",12)

Inną możliwością byłoby:

def person = new Person("Kumar",12)
val p = person
p.age=20
println(p.age)

Jednak person.age=20w twoim kodzie jest to dozwolone, gdy odzyskasz Personinstancję z personmetody, aw tej instancji możesz zmienić wartość var. Problem polega na tym, że po tej linii nie ma już odniesienia do tej instancji (ponieważ każde wywołanie funkcji to personutworzy nową instancję).

To nic specjalnego, w Javie miałbyś dokładnie takie samo zachowanie:

class Person{ 
   public int age; 
   private String name;
   public Person(String name; int age) {
      this.name = name;  
      this.age = age;
   }
   public String name(){ return name; }
}

public Person person() { 
  return new Person("Kumar", 12); 
}

person().age = 20;
System.out.println(person().age); //--> 12
Landei
źródło
8

Weźmy to:

class Person(val name:String,var age:Int )
def person =new Person("Kumar",12)
person.age=20
println(person.age)

i przepisz go za pomocą równoważnego kodu

class Person(val name:String,var age:Int )
def person =new Person("Kumar",12)
(new Person("Kumar", 12)).age_=(20)
println((new Person("Kumar", 12)).age)

Widzisz, defto metoda. Wykona się za każdym razem, gdy zostanie wywołana i za każdym razem zwróci (a) new Person("Kumar", 12). I to nie jest błąd w „przypisaniu”, ponieważ tak naprawdę nie jest to przypisanie, a jedynie wywołanie age_=metody (dostarczonej przez var).

Daniel C. Sobral
źródło