在Scala中,def、val和var的用法是什么?

164
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 person = new Person("Kumar",12)中使用了def。如果我使用var或val,则输出为20。我了解在Scala中默认情况下是val。

def age = 30
age = 45

默认情况下它是一个 val,所以会导致编译错误。为什么上面的第一组代码不能正常工作,却又没有报错?

6个回答

261

在 Scala 中有三种定义方式:

  • def 定义一个方法
  • val 定义一个固定的(不能被修改)
  • var 定义一个变量(可以被修改)

看你的代码:

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

这定义了一个叫做person的新方法。你只能调用这个没有参数的方法而不需要使用()。对于有空括号的方法,你可以带或不带'()'来调用它。如果你只写:

person

那么你正在调用这个方法(如果你不将返回值分配给任何变量,它将被丢弃)。在以下代码行中:

person.age = 20

发生的情况是,您首先调用了person方法,然后在返回值(类Person的实例)上更改了age成员变量。

最后一行:

println(person.age)

你又调用了person方法,该方法返回一个新的Person类实例(age设为12)。这等同于:

println(person().age)

29
为了避免混淆,val变量的内部状态可以进行更改,但该变量所引用的对象不能被改变。val不是常量。 - pferrel
5
为了让事情更加混乱,val(也许var也是,我没有尝试过)可以用来定义一个函数。使用def来定义一个函数/方法时,每次调用def的主体都会被评估。而使用val时,它仅在定义点进行评估。请参见https://dev59.com/J2Mk5IYBdhLWcg3w3xvq。 - melston
3
更令人困惑的是,"def" 还可用于定义类的成员变量,而不一定使用 "var"。 - Peiti Li
2
@pferrel并不是很令人困惑。与Java的final相同。您可以将List标记为final,但可以修改其内容。 - jFrenetic
1
值得注意的是,尽管我们可以使用存储在“val”中的单态函数来模拟“def”行为,但需要注意重载和语法细节,但“def”也可以具有类型参数,因此变得具有参数多态性(泛型)。这对于“val”而言是不可能的,除非在内部使用“def”。 - P. Frolov
显示剩余2条评论

104

首先要区分 Scala 中 defvalvar 之间的区别。

  • def - 定义了一个不可变标签,其右侧内容是惰性求值的——按名称求值。

  • val - 定义了一个不可变标签,其右侧内容是急切/立即求值的——按值求值。

  • var - 定义了一个可变变量,最初设置为求值后的右侧内容。

例如, def:

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

根据以上内容,defval标签不能被重新赋值,如果尝试这样做将会出现如下错误:

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程序员心中必须清楚的事实。


我认为上面的解释不正确。请看其他答案。 - Per Mildner
@PerMildner,您能否详细说明上面的答案有什么问题? - logdev
我不记得我的原始投诉是什么了。然而,答案的最后一部分关于personA等似乎有些不对。无论修改age成员是否有效,都与您使用def personAvar personB无关。区别在于,在def personA情况下,您正在修改从第一次评估personA返回的Person实例。该实例被修改了,但当您再次评估personA时,它不是返回的内容。相反,第二次执行personA.age时,您实际上正在执行new Person("Tim",25).age - Per Mildner

29

使用

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

你正在定义一个函数/惰性变量,它总是返回一个名为“Kumar”且年龄为12的新Person实例。这是完全有效的,编译器没有理由抱怨。调用person.age将返回这个新创建的Person实例的年龄,它始终为12。
当编写时,
person.age = 45

在类Person中,您将为age属性分配一个新值,这是有效的,因为age被声明为var。如果您尝试使用新的Person对象重新分配person,编译器将会抱怨。

person = new Person("Steve", 13)  // Error

是的,通过在 personA 上调用 hashCode 方法可以轻松地证明这一点。 - Nilanjan Sarkar

27
为了提供另一个角度,Scala中的“def”表示每次使用时都会进行计算,而val表示立即进行一次计算。在这里,表达式def person = new Person("Kumar",12)意味着无论何时我们使用“person”,都会调用new Person("Kumar",12)。因此,两个“person.age”是无关的。
这是我理解Scala的方式(可能更符合“函数式”)。我不确定。
def defines a method
val defines a fixed value (which cannot be modified)
var defines a variable (which can be modified)

尽管如此,这确实是Scala的意图。至少我不太喜欢那样想...


20

正如 Kintaro 已经说过的那样,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 实例,并且在这个实例上可以更改一个 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

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提供)的调用。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接