Использование def, val и var в scala

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

Эти строки кода выводятся 12, хотя person.age=20были успешно выполнены. Я обнаружил, что это происходит, потому что я использовал def в def person = new Person("Kumar",12). Если я использую VAR или VAL, вывод 20. Я понимаю, что по умолчанию это значение val в scala. Это:

def age = 30
age = 45

... выдает ошибку компиляции, потому что это значение по умолчанию. Почему первый набор строк выше не работает должным образом, и все же также не ошибается?

Бью Вееду
источник

Ответы:

254

Существует три способа определения вещей в Scala:

  • defопределяет метод
  • valопределяет фиксированное значение (которое нельзя изменить)
  • varопределяет переменную (которая может быть изменена)

Глядя на ваш код:

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

Это определяет новый метод с именем person. Вы можете вызывать этот метод только без, ()потому что он определен как метод без параметров. Для метода empty-paren вы можете вызывать его с или без '()'. Если вы просто напишите:

person

тогда вы вызываете этот метод (и если вы не назначите возвращаемое значение, оно просто будет отброшено). В этой строке кода:

person.age = 20

происходит то, что вы сначала вызываете personметод, а в возвращаемом значении (экземпляре класса Person) вы изменяете ageпеременную-член.

И последняя строка:

println(person.age)

Здесь вы снова вызываете personметод, который возвращает новый экземпляр класса Personageустановленным значением 12). Это так же, как это:

println(person().age)
Jesper
источник
27
Чтобы запутать вещи, внутреннее состояние valможет быть изменено, но объект, на который ссылается val, не может. А valне является константой.
pferrel
5
Чтобы еще больше сбить с толку, для определения функции можно использовать val (и, может быть, var, я не пробовал). При использовании def для определения функции / метода тело def вычисляется при каждом вызове. При использовании val он оценивается только в точке определения. См stackoverflow.com/questions/18887264/...
melston
1
@ melston Да, но метод и функция тоже не одно и то же .
Джеспер
3
Чтобы еще больше запутать вещи, def может также использоваться для определения переменных-членов класса, необязательно использовать var.
Пейти Ли
2
@pferrel не совсем сбивает с толку. То же, что и в финале Java. Вы можете пометить Listкак final, но можете изменить его содержимое.
jFrenetic
100

Я бы начал с различия, которое существует в Scala между def , val и var .

  • def - определяет неизменяемый ярлык для содержимого правой стороны, которое лениво оценивается - оценивается по имени.

  • val - определяет неизменяемую метку для правого содержимого, которое охотно / немедленно оценивается - оценивается по значению.

  • var - определяет изменяемую переменную , изначально установленную на оцениваемое содержимое правой стороны.

Пример, определение

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

Пример, вал

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

Пример, вар

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

scala> aVariable = 5
aVariable: Int = 5

В соответствии с вышесказанным, метки от def и val не могут быть переназначены, и в случае любой попытки появится ошибка, подобная приведенной ниже:

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

Когда класс определен как:

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

и затем создается с помощью:

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

для этого конкретного экземпляра Person создается неизменяемый ярлык (т. е. personA). Всякий раз, когда необходимо изменить изменяемое поле 'age', такая попытка завершается неудачно:

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

как и ожидалось, age является частью неизменяемой метки. Правильный способ работы с этим заключается в использовании изменяемой переменной, как в следующем примере:

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

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

как ясно, из ссылки на изменяемые переменные (то есть 'personB') можно изменить изменяемое поле класса 'age'.

Я бы все-таки подчеркнул тот факт, что все проистекает из вышеуказанного различия, которое должно быть понятно любому программисту Scala.

Паоло Мареска
источник
Я не думаю, что приведенное выше объяснение является правильным. Смотрите другие ответы.
По Милднер
@PerMildner Не могли бы вы уточнить, что не так в ответе выше?
Сайед Субан
Я не помню, какова была моя первоначальная жалоба. Однако последняя часть ответа о personAсоавт. кажется выключенным ageРаботает ли изменение члена или нет, не зависит от того, используете ли вы def personAили var personB. Разница в том, что в def personAслучае, если вы Personизменяете экземпляр, возвращенный после первой оценки personA. Этот экземпляр будет изменен, но это не то , что возвращается , когда вы в очередной раз оценить personA. Вместо этого, во второй раз, вы делаете personA.ageэто эффективно new Person("Tim",25).age.
По
29

С участием

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

вы определяете переменную function / lazy, которая всегда возвращает новый экземпляр Person с именем "Kumar" и возрастом 12. Это полностью допустимо, и у компилятора нет причин жаловаться. Вызов person.age вернет возраст этого вновь созданного экземпляра Person, который всегда равен 12.

При написании

person.age = 45

Вы присваиваете новое значение для свойства age в классе Person, которое является действительным, поскольку возраст объявлен как var. Компилятор будет жаловаться, если вы попытаетесь переназначить personновый объект Person, например

person = new Person("Steve", 13)  // Error
Кинтаро
источник
Да. Эту точку можно легко продемонстрировать, вызвав метод hashCode для personA
Ниланджан Саркар
26

Чтобы представить другую перспективу, «def» в Scala означает то, что будет оцениваться каждый раз, когда он используется, тогда как val - это то, что оценивается сразу и только один раз . Здесь выражение def person = new Person("Kumar",12)влечет за собой то, что всякий раз, когда мы используем «лицо», мы получим new Person("Kumar",12)вызов. Поэтому естественно, что два «персонажа» не связаны между собой.

Я так понимаю Scala (возможно, в более «функциональной» манере). Я не уверен, если

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

Это действительно то, что Скала намеревается иметь в виду, хотя. Мне не очень нравится так думать, по крайней мере ...

xji
источник
20

Как уже сказал Кинтаро, person - это метод (из-за def), который всегда возвращает новый экземпляр Person. Как вы узнали, это будет работать, если вы измените метод на var или val:

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

Другая возможность будет:

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

Тем не менее, person.age=20в вашем коде это разрешено, так как вы возвращаете Personэкземпляр из personметода, и в этом случае вам разрешено изменять значение a var. Проблема в том, что после этой строки у вас больше нет ссылки на этот экземпляр (так как каждый вызов personсоздаст новый экземпляр).

В этом нет ничего особенного, у вас будет точно такое же поведение в 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
источник
8

Давайте возьмем это:

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

и переписать его с эквивалентным кодом

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)

Видите, defэто метод. Он будет выполняться каждый раз, когда вызывается, и каждый раз будет возвращать (a) new Person("Kumar", 12). И это не ошибка в «присваивании», потому что на самом деле это не присваивание, а просто вызов age_=метода (предоставленного var).

Даниэль С. Собрал
источник