Utilisation de def, val et var dans scala

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

Ces lignes de code sortent 12, même si elles ont person.age=20été exécutées avec succès. J'ai trouvé que cela se produit parce que j'ai utilisé def in def person = new Person("Kumar",12). Si j'utilise var ou val, la sortie est 20. Je comprends que la valeur par défaut est val dans scala. Ce:

def age = 30
age = 45

... donne une erreur de compilation car c'est un val par défaut. Pourquoi le premier ensemble de lignes ci-dessus ne fonctionne-t-il pas correctement et ne fait-il pas d'erreur?

Byju Veedu
la source

Réponses:

254

Il existe trois façons de définir les choses dans Scala:

  • defdéfinit une méthode
  • valdéfinit une valeur fixe (qui ne peut pas être modifiée)
  • vardéfinit une variable (qui peut être modifiée)

En regardant votre code:

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

Ceci définit une nouvelle méthode appelée person. Vous ne pouvez appeler cette méthode que sans ()car elle est définie comme méthode sans paramètre. Pour la méthode empty-paren, vous pouvez l'appeler avec ou sans '()'. Si vous écrivez simplement:

person

alors vous appelez cette méthode (et si vous n'affectez pas la valeur de retour, elle sera simplement supprimée). Dans cette ligne de code:

person.age = 20

ce qui se passe, c'est que vous appelez d'abord la personméthode, et sur la valeur de retour (une instance de classe Person) vous modifiez la agevariable membre.

Et la dernière ligne:

println(person.age)

Ici, vous appelez à nouveau la personméthode, qui renvoie une nouvelle instance de classe Person(avec une valeur agede 12). C'est la même chose:

println(person().age)
Jesper
la source
27
Pour brouiller les choses, l'état interne de a valpeut être changé mais l'objet auquel se réfère un val ne le peut pas. A valn'est pas une constante.
pferrel
5
Pour compliquer davantage les choses, val (et peut-être var aussi, je ne l'ai pas essayé) peut être utilisé pour définir une fonction. Lorsque vous utilisez def pour définir une fonction / méthode, le corps de def est évalué à chaque fois qu'il est appelé. Lors de l'utilisation de val, il n'est évalué qu'au point de définition. Voir stackoverflow.com/questions/18887264/…
melston
1
@melston Oui, mais une méthode et une fonction ne sont pas exactement la même chose .
Jesper
3
pour confondre encore plus les choses, def peut également être utilisé pour définir des variables membres d'une classe, pas nécessairement pour utiliser var.
Peiti Li
2
@pferrel pas vraiment déroutant. Identique à la finale de Java. Vous pouvez marquer un Listcomme final, mais vous pouvez modifier son contenu.
jFrenetic
100

Je commencerais par la distinction qui existe dans Scala entre def , val et var .

  • def - définit une étiquette immuable pour le contenu du côté droit qui est évalué paresseusement - évalué par nom.

  • val - définit une étiquette immuable pour le contenu de droite qui est évalué avec empressement / immédiatement - évalué par valeur.

  • var - définit une variable mutable , initialement définie sur le contenu du côté droit évalué.

Exemple, def

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

Exemple, val

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

Exemple, var

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

scala> aVariable = 5
aVariable: Int = 5

Selon ci-dessus, les étiquettes de def et val ne peuvent pas être réaffectées, et en cas de tentative, une erreur comme celle ci-dessous sera déclenchée:

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

Lorsque la classe est définie comme:

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

puis instancié avec:

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

une étiquette immuable est créée pour cette instance spécifique de Person (c'est-à-dire «personA»). Chaque fois que le champ mutable 'age' doit être modifié, une telle tentative échoue:

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

comme prévu, «l'âge» fait partie d'une étiquette non mutable. La bonne façon de travailler consiste à utiliser une variable mutable, comme dans l'exemple suivant:

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

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

comme clair, à partir de la référence de variable mutable (c'est-à-dire 'personB'), il est possible de modifier le champ de classe mutable 'age'.

J'insisterais encore sur le fait que tout vient de la différence mentionnée ci-dessus, qui doit être claire à l'esprit de tout programmeur Scala.

Paolo Maresca
la source
Je ne pense pas que l'explication ci-dessus soit correcte. Voir les autres réponses.
Par Mildner
@PerMildner Pouvez-vous expliquer ce qui ne va pas dans la réponse ci-dessus?
Syed Souban
Je ne me souviens pas de ma plainte initiale. Cependant, la dernière partie de la réponse, sur personAet al. semble éteint. Que la modification du agemembre fonctionne ou non est indépendante du fait que vous utilisez def personAou var personB. La différence est que dans le def personAcas où vous modifiez l' Personinstance renvoyée par votre première évaluation de personA. Cette instance est modifiée, mais ce n'est pas ce qui est renvoyé lorsque vous évaluez à nouveau personA. Au lieu de cela, la deuxième fois que vous faites, personA.agevous faites effectivement new Person("Tim",25).age.
Par Mildner
29

Avec

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

vous définissez une fonction / variable paresseuse qui renvoie toujours une nouvelle instance de Person avec le nom "Kumar" et l'âge de 12 ans. Ceci est totalement valide et le compilateur n'a aucune raison de se plaindre. L'appel de person.age renverra l'âge de cette instance Person nouvellement créée, qui est toujours de 12.

Lors de l'écriture

person.age = 45

vous attribuez une nouvelle valeur à la propriété age dans la classe Person, qui est valide puisque age est déclaré comme var. Le compilateur se plaindra si vous essayez de réaffecter personavec un nouvel objet Person comme

person = new Person("Steve", 13)  // Error
Kintaro
la source
Oui. Ce point peut facilement être démontré en appelant la méthode hashCode sur le personA
Sarkar
26

Pour fournir une autre perspective, "def" dans Scala signifie quelque chose qui sera évalué à chaque fois qu'il sera utilisé, tandis que val est quelque chose qui est évalué immédiatement et une seule fois . Ici, l'expression def person = new Person("Kumar",12)implique que chaque fois que nous utilisons "personne", nous recevrons un new Person("Kumar",12)appel. Il est donc naturel que les deux "person.age" ne soient pas liés.

C'est ainsi que je comprends Scala (probablement d'une manière plus "fonctionnelle"). Je ne sais pas si

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

c'est vraiment ce que Scala entend signifier. Je n'aime pas vraiment penser de cette façon au moins ...

xji
la source
20

Comme Kintaro le dit déjà, person est une méthode (à cause de def) et renvoie toujours une nouvelle instance de Person. Comme vous l'avez découvert, cela fonctionnerait si vous changez la méthode en var ou val:

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

Une autre possibilité serait:

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

Cependant, person.age=20dans votre code est autorisé, lorsque vous récupérez une Personinstance de la personméthode, et sur cette instance, vous êtes autorisé à modifier la valeur de a var. Le problème est qu'après cette ligne, vous n'avez plus de référence à cette instance (car chaque appel à personproduira une nouvelle instance).

Cela n'a rien de spécial, vous auriez exactement le même comportement en Java:

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
la source
8

Prenons ceci:

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

et réécrivez-le avec un code équivalent

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)

Voyez, defest une méthode. Il s'exécutera chaque fois qu'il sera appelé, et à chaque fois il retournera (a) new Person("Kumar", 12). Et ce n'est pas une erreur dans l '"affectation" car ce n'est pas vraiment une affectation, mais juste un appel à la age_=méthode (fournie par var).

Daniel C. Sobral
la source