Scala类构造函数参数

51
什么是以下两者之间的区别:
class Person(name: String, age: Int) {
  def say = "My name is " + name + ", age " + age
}
and
class Person(val name: String, val age: Int) { 
  def say = "My name is " + name + ", age " + age
}

我能将参数声明为 var 并在以后更改它们的值吗?例如,

class Person(var name: String, var age: Int) {

  age = happyBirthday(5)

  def happyBirthday(n: Int) {
    println("happy " + n + " birthday")
    n
  }
}

6个回答

54

第一部分的答案是范围:

scala> class Person(name: String, age: Int) {
     |   def say = "My name is " + name + ", age " + age
     | }

scala> val x = new Person("Hitman", 40)

scala> x.name
<console>:10: error: value name is not a member of Person
              x.name
如果您使用valvar前缀来声明参数,则它们将可以从类的外部访问;否则,它们将是私有的,就像您在上面的代码中所看到的那样。
而且,是的,您可以像通常一样更改var的值。

32
这里讲的比公有/私有区分略微更微妙。如果没有属性关键字(valvar),则构造函数参数仅在方法体中使用时才会存储在字段中。如果它们仅在构造函数代码中使用且未带有varval前缀,则它们根本不存在于构造函数之外。如果实例大小很重要,请注意非属性构造函数参数是否在方法体中引用。 - Randall Schulz
2
此外,作用域和可访问性是不同的概念。 - Randall Schulz
1
@RandallSchulz,那么这段Scala代码是如何编译和工作的呢?(https://gist.github.com/gnapse/96387a056f0cf45dac7a)。`Testing`类的`fake`参数没有以`val`或`var`为前缀,但它仍然可以在实例方法中访问(查看`output`方法),而不仅仅是在构造函数代码中。 - Ernesto
2
在这种情况下,Scala编译器将会生成一个私有字段,如果你查看生成的字节码,你会看到 private final Z fake - Nader Ghanbari

11

这个

class Person(val name: String, val age: Int)

使字段对类的外部用户可用,例如,您可以稍后执行
val p = new Person("Bob", 23)
val n = p.name

如果您将参数指定为var,则作用域与val相同,但字段是可变的。


8
如果你熟悉Java,可以从这个例子中理解:

class Person(name: String, age: Int)

与之相似。
class Person {
  public Person(String name, int age) {
  }
}

当...时,
class Person(var name: String, var age: Int) // also we can use 'val'

与...相似

class Person {
  String name;
  int age;

  public Person(String name, int age) {
     this.name = name;
     this.age = age;
  }
}

如果没有var/val,变量只能在构造函数内部访问。若添加了var/val,类将具有相同名称的成员变量。


3
第一个比较并不完全正确。在Java代码中,你只能在构造函数中调用参数nameage,而不能在其他类方法中使用,但在Scala中是可以的。在Java中,类似的代码如下:class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } } - Code Pope
1
附加说明:这两个字段也是不可变的。 - Code Pope

2
这里的答案非常好,但是我想通过探索字节码来解决这个问题。当您对一个类应用javap时,它会打印出传递的类的包、受保护和公共字段以及方法。我创建了一个名为Person.scala的类,并使用以下代码填充它。
class Person(name: String, age: Int) {
  def say = "My name is " + name + ", age " + age
}

class PersonVal(val name: String, val age: Int) {
  def say = "My name is " + name + ", age " + age
}

class PersonVar(var name: String, var age: Int) {

  age = happyBirthday(5)

  def happyBirthday(n: Int) = {
    println("happy " + n + " birthday")
    n
  }
}

使用scalac Person.scala编译代码后,会生成三个文件,分别为Person.class, PersonVal.calass , PersonVar.cass。通过对每个类文件运行javap,我们可以看到它们的结构如下:

>>javap Person.class
Compiled from "Person.scala"
public class Person {
  public java.lang.String say();
  public Person(java.lang.String, int);
}

在这种情况下,由于Person类既没有使用val也没有使用var进行声明,因此不会创建任何类变量,因此name和age只能在构造函数内部使用。
>>javap PersonVal.class
public class PersonVal {
  public java.lang.String name();
  public int age();
  public java.lang.String say();
  public PersonVal(java.lang.String, int);
}

在这种情况下,它有三个成员,两个用于输入构造函数,一个用于我们在构造函数中声明的成员。然而,我们没有为输入构造函数设置任何setter,因此无法更改值。

>>javap PersonVar.class
public class PersonVar {
  public java.lang.String name();
  public void name_$eq(java.lang.String);
  public int age();
  public void age_$eq(int);
  public int happyBirthday(int);
  public PersonVar(java.lang.String, int);
}

这与PersonVal示例相同,但我们可以使用 variable_$eq 方法更改此情况下的值。它只是 variable = 的缩写。


0

@Reza的回答帮助我最好地澄清了使用javap探索字节码的概念。为了引用这种情况的一个非常具体的例子,请参考我在生产Web应用程序(Play + Scala)中遇到的以下场景: 如何在Scala中将参数注入类/特征方法

如果我不使用val前缀来注入参数authorizationHandler,那么编译器会抛出此错误:

class MyController needs to be abstract, since method authorizationHandler in trait AuthorizationCheck of type => controllers.authapi.AuthorizationHandler is not defined
[error] class MyController @Inject() (authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {
[error]       ^
[error] one error found

遗憾的是,这个错误没有帮助我准确定位到正确的问题,即需要使用 val 前缀。

class MyController @Inject()(val authorizationHandler: AuthorizationHandler) extends Controller with AuthorizationCheck {

   def myAction = AuthenticatedAction { implicit request =>
     ...
   }
} 

0
你可以使用一个 case class,在这种情况下,Person 类将使这些变量在类外可用。 case class Person(name: String, age: Int)。然后以下代码将按预期工作。 val z = new Person("John", 20); z.name //John

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