【问题标题】:In Scala, how do you define a local parameter in the primary constructor of a class?在 Scala 中,如何在类的主构造函数中定义局部参数?
【发布时间】:2009-12-11 17:09:13
【问题描述】:

在 Scala 中,如何在非数据成员的类的主构造函数中定义局部参数,例如,该参数仅用于初始化基类中的数据成员?

例如,在下面的代码中,如何在类B的主构造函数中正确定义参数b,使其只生成临时局部参数而不生成数据成员?

class A(var a: Int)
class B(?b?) extends A(b)

Randall,你的回答解释了为什么 Scala 编译器在我引入一个增加属性 a 的方法 inc 时会抱怨,而且还更改了类 B 构造函数中的参数名称以匹配参数的名称在类A构造函数中:

class A(var a: Int)
class B(a: Int) extends A(a) {
  def inc(value: Int) { this.a += value }
}

Scala 编译器输出:

$ scala construct.scala
construct.scala:3: error: reassignment to val
  def inc(value: Int) { this.a += value }
                               ^
one error found

Scala 抱怨是因为类B 现在必须有一个私有的只读属性a,因为在inc 中引用了a。将B(a: Int) 更改为B(var a: Int) 会产生不同的编译器错误:

construct.scala:2: error: error overriding variable a in class A of type Int;
 variable a needs `override' modifier
class B(var a: Int) extends A(a) {
            ^
one error found

添加override 也无济于事:

construct.scala:2: error: error overriding variable a in class A of type Int;
 variable a cannot override a mutable variable
class B(override var a: Int) extends A(a) {
                 ^
one error found

如何在B的主构造函数中的参数中使用与基类A的主构造函数中定义的属性相同的名称?

【问题讨论】:

    标签: scala constructor


    【解决方案1】:

    如果从构造函数参数中删除“var”或“val”关键字,则不会产生属性。

    但请注意,非 var、非 val 构造函数参数在范围内并且可在整个类中访问。如果您在非构造函数代码中使用一个(即在方法的主体中),则生成的类中将有一个不可见的私有字段来保存该构造函数参数,就像您将其设为“私有变量”或“ private val" 构造函数参数。

    附录(迟到总比没有好??):

    在这段代码中,对构造函数参数的引用只出现在构造函数体中:

    class C1(i: Int) {
      val iSquared = i * i
      val iCubed = iSquared * i
      val iEven = i - i % 2
    }
    

    ...这里i的值只在构造函数执行期间存在。

    但是,在下面的代码中,因为构造函数参数是在方法体中引用的——它不是构造函数体的一部分——所以必须将构造函数参数复制到生成类的(私有)字段中(增加其内存保存Int所需的4个字节):

    class C2(i: Int) {
      val iSquared = i * i
      val iCubed = iSquared * i
      val iEven = i - i % 2
    
      def mod(d: Int) = i % d
    }
    

    【讨论】:

    • 兰德尔,谢谢您的回答。您能否举例说明您的警告?
    • 所以如果没有参数成为实例的属性,就不可能有一个参数给构造函数?我有一个接收布尔值的类,初始化根据布尔值而变化,但是在构造函数结束后我不需要布尔值。尽管如此,这个类的实例都会有一个额外的属性(前面提到的布尔值)占用内存,即使在初始化之后不需要。对于像 Scala 这样出色的语言来说,这当然很糟糕。伤心:(
    • @Ernesto,是的,正如 Randall 在他的附录中解释的那样,可以有一个本地构造函数参数,前提是类中没有方法引用该参数。在存在这种方法的情况下,Scala 将参数“提升”为实例数据成员。
    • 嗯,这解释了很多。显然我没有正确阅读答案,因为我现在很清楚。该功能虽然有点隐藏。我希望它不会促进任何东西,并引发编译错误。毕竟,如果你真的想让参数成为一个属性,你总是可以在它前面加上valvar。这样会更清楚。
    【解决方案2】:

    经过一些实验,我确定在参数b 前面省略varval 将使其成为本地参数而不是数据成员:

    class A(var a: Int)
    class B(b: Int) extends A(b)
    

    Java 扩展:

    $ javap -private B
    Compiled from "construct.scala"
    public class B extends A implements scala.ScalaObject{
        public B(int);
    }
    
    $ javap -private A
    Compiled from "construct.scala"
    public class A extends java.lang.Object implements scala.ScalaObject{
        private int a;
        public A(int);
        public void a_$eq(int);
        public int a();
        public int $tag()       throws java.rmi.RemoteException;
    }
    

    请注意,类A 有一个私有数据成员a,因为它的主构造函数中有var a: Int。然而,B 类没有数据成员,但它的主构造函数仍然有一个整数参数。

    【讨论】:

    • 看来如果你把B做成一个case类,b就会变成一个数据成员。知道为什么吗?
    • 我也做了一些实验,得出了相反的结论。即使不使用 valvar 为参数添加前缀,它仍然会超出实例的构造函数/初始化阶段。否则,您如何解释这段代码可以编译和运行? (gist.github.com/gnapse/96387a056f0cf45dac7a) 注意fake 构造函数参数是如何在output 实例方法中访问的。
    • @Ernesto,您使用的是哪个版本的 Scala 编译器?大约五年前,我可能使用当时的最新版本进行了此测试。行为可能已经改变。
    • 是的,我现在意识到这个帖子是从2009年开始的。如果是这样的话,那很遗憾当时一切都很好,现在却错了。难以想象 Scala 不允许有不打算成为属性的构造函数参数。 (我使用的 Scala 版本是 2.11.4)。
    【解决方案3】:

    您可以像这样在单个类成员的初始化过程中创建临时变量:

    class A(b:Int){
      val m = {
        val tmp = b*b
        tmp+tmp
      }
    }
    

    【讨论】:

    • 所以mA 类的实例初始化为2*b*b 的属性?
    • 没错。您还可以在构造函数内用大括号括起来。也许在那里实例化的本地人也不会被创建为类成员。
    【解决方案4】:

    德里克,

    如果你有这个:

    class A(a: Int) {
      val aa = a // reference to constructor argument in constructor code (no problem)
      def m: Float = a.toFloat // reference to constructor argument in method body (causes a to be held in a field)
    }
    

    你会发现(例如使用 javap)类中存在一个名为“a”的字段。如果您注释掉“def m”,您将看到该字段未创建。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-17
      • 1970-01-01
      • 2023-03-12
      • 2015-12-14
      相关资源
      最近更新 更多