【问题标题】:scala class constructor parametersscala类构造函数参数
【发布时间】:2013-03-16 08:48:26
【问题描述】:

两者有什么区别:

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

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

我可以将参数声明为vars,然后再更改它们的值吗?例如,

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

  age = happyBirthday(5)

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

【问题讨论】:

标签: scala constructor scope immutability


【解决方案1】:

这个

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

使字段对类的用户外部可用,例如你以后可以做

val p = new Person("Bob", 23)
val n = p.name

如果您将 args 指定为 var,则范围与 val 相同,但字段是可变的。

【讨论】:

    【解决方案2】:

    对于第一部分,答案是范围:

    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 的值。

    【讨论】:

    • 它比公共/私人区别更微妙。如果没有属性关键字(valvar),构造函数参数只有在方法体中使用时才会存储在字段中。如果它们只在构造函数代码中使用并且没有前缀varval,那么它们在构造函数之外根本不存在。如果实例的大小很重要,请注意您的非属性构造函数参数是否在方法体中被引用。
    • 另外,范围和可访问性是不同的东西。
    • @RandallSchulz 那么这个 Scala 代码怎么可能编译和工作呢? (gist.github.com/gnapse/96387a056f0cf45dac7a)。 Testing 类的 fake 参数不以 valvar 为前缀,并且它仍然可以在实例方法中访问(查看 output 方法),而不仅仅是在构造函数代码中。
    • Scala 编译器在这种情况下会发出一个私有字段,如果你查看生成的字节码,你会看到private final Z fake
    【解决方案3】:

    如果你熟悉 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,则该类将具有同名的成员变量。

    【讨论】:

    • 第一个比较并不完全正确。在 Java 代码中,您可以在构造函数中调用参数 nameage,而不是在其他类方法中,但在 Scala 中可以。 Java 中类似的代码是:class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } }
    • 补充:这两个字段也是不可变的。
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      这里的答案非常好,但是我正在通过探索字节码来解决这个问题。当您在一个类上应用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 也没有使用 val 声明,因此名称和年龄只能在构造函数中使用。

      >>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);
      }
      

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

      >>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 = 的缩短版

      【讨论】:

        【解决方案6】:

        @Reza 作者使用 javap 探索字节码的答案帮助我最好地阐明了这个概念。要引用此案例的一个非常具体的示例,请参考我在生产 Web 应用程序(Play + Scala)中遇到的以下场景: How to inject parameters into a class/trait method in 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 =>
             ...
           }
        } 
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-09-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-11-09
          • 2012-11-18
          相关资源
          最近更新 更多