Uso de def, val e var em scala

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

Essas linhas de saída de código 12, mesmo que tenham person.age=20sido executadas com sucesso. Descobri que isso acontece porque usei def in def person = new Person("Kumar",12). Se eu usar var ou val, a saída é 20. Entendo que o padrão é val in scala. Este:

def age = 30
age = 45

... fornece um erro de compilação porque é um valor por padrão. Por que o primeiro conjunto de linhas acima não funciona corretamente e também não gera erros?

Byju Veedu
fonte

Respostas:

254

Existem três maneiras de definir as coisas no Scala:

  • defdefine um método
  • valdefine um valor fixo (que não pode ser modificado)
  • vardefine uma variável (que pode ser modificada)

Olhando para o seu código:

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

Isso define um novo método chamado person. Você pode chamar esse método apenas sem, ()porque é definido como método sem parâmetros. Para o método de parênteses vazias, você pode chamá-lo com ou sem '()'. Se você simplesmente escrever:

person

então você está chamando esse método (e se você não atribuir o valor de retorno, ele será descartado). Nesta linha de código:

person.age = 20

o que acontece é que você chama o personmétodo pela primeira vez e, no valor de retorno (uma instância da classe Person), está alterando a agevariável de membro.

E a última linha:

println(person.age)

Aqui você está novamente chamando o personmétodo, que retorna uma nova instância da classe Person(com agedefinido como 12). É o mesmo que isto:

println(person().age)
Jesper
fonte
27
Para confundir as coisas, o estado interno de a valpode ser alterado, mas o objeto referido por uma val não pode. A valnão é uma constante.
Pferrel #
5
Para confundir ainda mais as coisas, val (e talvez var, também não tentei) pode ser usado para definir uma função. Ao usar def para definir uma função / método, o corpo do def é avaliado toda vez que é chamado. Ao usar val, ela é avaliada apenas no ponto de definição. Veja stackoverflow.com/questions/18887264/…
melston
1
@ melston Sim, mas um método e uma função também não são exatamente a mesma coisa .
Jesper
3
para confundir ainda mais as coisas, def também pode ser usado para definir variáveis ​​de membros de uma classe, não necessariamente para usar var.
Peiti Li 13/08/2015
2
@ pferrel não é realmente confuso. O mesmo da final do Java. Você pode marcar um Listcomo final, mas pode modificar seu conteúdo.
jFrenetic
100

Eu começaria pela distinção que existe em Scala entre def , val e var .

  • def - define um rótulo imutável para o conteúdo do lado direito que é avaliado preguiçosamente - avalie pelo nome.

  • val - define um rótulo imutável para o conteúdo do lado direito que é avidamente / imediatamente avaliado - avaliado por valor.

  • var - define uma variável mutável , inicialmente definida como o conteúdo avaliado do lado direito.

Exemplo, def

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

Exemplo, val

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

Exemplo, var

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

scala> aVariable = 5
aVariable: Int = 5

De acordo com o acima, as etiquetas def e val não podem ser reatribuídas e, em caso de tentativa, será gerado um erro como o abaixo:

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

Quando a classe é definida como:

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

e instanciado com:

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

um rótulo imutável é criado para essa instância específica de Person (ou seja, 'personA'). Sempre que o campo mutável 'idade' precisar ser modificado, essa tentativa falha:

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

como esperado, 'idade' faz parte de um rótulo não mutável. A maneira correta de trabalhar nisso consiste em usar uma variável mutável, como no exemplo a seguir:

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

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

Como claro, a partir da referência da variável mutável (ou seja, 'personB'), é possível modificar o campo mutável da classe 'age'.

Eu ainda enfatizaria o fato de que tudo deriva da diferença acima mencionada, que deve estar clara em mente para qualquer programador Scala.

Paolo Maresca
fonte
Não acho que a explicação acima esteja correta. Veja as outras respostas.
Por Mildner
@PerMildner Você pode explicar o que está errado na resposta acima?
Syed Souban 09/07/19
Não me lembro qual era minha reclamação original. No entanto, a última parte da resposta, sobre personAet al. parece desligado. Se a modificação do agemembro funciona ou não, é independente de você usar def personAou var personB. A diferença é que, no def personAcaso de você estar modificando a Personinstância-retornada de sua primeira avaliação de personA. Essa instância é modificada, mas não é o que é retornado quando você avalia novamente personA. Em vez disso, na segunda vez em que personA.agevocê efetivamente está fazendo new Person("Tim",25).age.
Por Mildner
29

Com

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

você está definindo uma variável function / lazy que sempre retorna uma nova instância Person com o nome "Kumar" e 12 anos. Isso é totalmente válido e o compilador não tem motivos para reclamar. Chamar person.age retornará a idade dessa instância Person recém-criada, que é sempre 12.

Ao escrever

person.age = 45

você atribui um novo valor à propriedade age na classe Person, que é válida desde que a idade seja declarada como var. O compilador reclamará se você tentar reatribuir personcom um novo objeto Person como

person = new Person("Steve", 13)  // Error
Kintaro
fonte
Sim. Esse ponto pode ser facilmente demonstrado chamando o método hashCode na pessoa A
Nilanjan Sarkar
26

Para fornecer outra perspectiva, "def" no Scala significa algo que será avaliado toda vez que for usado, enquanto val é algo que é avaliado imediatamente e apenas uma vez . Aqui, a expressão def person = new Person("Kumar",12)implica que sempre que usarmos "pessoa", receberemos uma new Person("Kumar",12)ligação. Portanto, é natural que os dois "person.age" não estejam relacionados.

É assim que entendo Scala (provavelmente de uma maneira mais "funcional"). Não tenho certeza se

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

é realmente o que Scala pretende dizer. Eu realmente não gosto de pensar assim, pelo menos ...

xji
fonte
20

Como o Kintaro já diz, person é um método (por causa de def) e sempre retorna uma nova instância de Person. Como você descobriu, funcionaria se você alterasse o método para um var ou val:

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

Outra possibilidade seria:

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

No entanto, person.age=20seu código é permitido, pois você recupera uma Personinstância do personmétodo e, nessa instância, você pode alterar o valor de a var. O problema é que, depois dessa linha, você não tem mais referência a essa instância (como cada chamada para personproduzirá uma nova instância).

Isso não é nada de especial, você teria exatamente o mesmo comportamento em 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
fonte
8

Vamos pegar o seguinte:

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

e reescreva-o com código equivalente

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)

Veja, defé um método. Ele será executado sempre que for chamado e sempre que retornar (a) new Person("Kumar", 12). E não há erro na "atribuição" porque não é realmente uma tarefa, mas apenas uma chamada para o age_=método (fornecido por var).

Daniel C. Sobral
fonte