Scala - Making sure that a field from the supercla

2019-08-08 03:30发布

问题:

In Scala, fields are declared in the primary constructor. And, if it happens to have the same name as a field from the super class, it will use the one passed in instead, which is what I am trying to avoid. Take the following example:

class Parent(protected val changedParam: MyClass)

class Child(changedParam: MyClass) extends Parent(doStuff(changedParam)) {
  def foo() = {
    bar(changedParam) // Uses Child.changedParam, not Parent.changedParam, which is what I want
  }
}

I want to make changedParam in Child to refer to the changedParam in Parent, not Child. Is there any way to do that?

回答1:

A parameter of a class is visible in its body. It will be made a private val if need be, that is if it is used in a method, and not just in the initialization code (and of course if it is not directly declared a val with a different visibility, or a var).

So changedParam in Child shadows the one in Base. The obvious way to avoid that is simply to call it another name:

class Child(anotherName: MyClass) extends Base(doStuff(anotherName)) {...


回答2:

This is considered brittle and annoying.

You get a little relief if you're shadowing a var:

scala> class A { var a = 1 } ; class B(a: Int) extends A { def f = a }
defined class A
defined class B

scala> val b = new B(42) ; b.a = 7 ; b.f
b: B = B@1376c05c
b.a: Int = 7
res0: Int = 42

scala> :replay -Xlint
Replaying: class A { var a = 1 } ; class B(a: Int) extends A { def f = a }
<console>:7: warning: private[this] value a in class B shadows mutable a inherited from class A.  Changes to a will not be visible within class B - you may want to give them distinct names.
       class A { var a = 1 } ; class B(a: Int) extends A { def f = a }
                                                                   ^
defined class A
defined class B

Replaying: val b = new B(42) ; b.a = 7 ; b.f
b: B = B@f2ff811
b.a: Int = 7
res0: Int = 42

Normally, shadowing a val is more benign, but a warning for your example would be useful.



回答3:

class Parent(protected val changedParam: MyClass)

trait Child extends Parent {
  def foo() = {
    bar(changedParam)
  }
}

object Child {
  def apply(changedParam: MyClass): Child =
    new Parent(doStuff(changedParam)) with Child
}

Not much to say... Turning Child into a trait avoids to declare a new temporary member that you don't want to be used after the initialization. Then I used the companion object to declare the constructor. The only difference in usage is that you don't have to add the new keyword to instanciate Child.

Test:

scala> :paste
// Entering paste mode (ctrl-D to finish)

class Parent(protected val changedParam: Int)

trait Child extends Parent {
  def foo() = {
    println(changedParam)
  }
}

object Child {
  def apply(changedParam: Int): Child =
    new Parent(1 + changedParam) with Child
}

// Exiting paste mode, now interpreting.

defined class Parent
defined trait Child
defined object Child

scala> Child(42).foo()
43


回答4:

1) Using explicit call to Parent with asInstanceOf:

package demo {
  // remove protected or make it protected[demo]
  class Parent(protected[demo] val changedParam: MyClass)

  class Child(changedParam: MyClass)
    extends Parent(doStuff(changedParam)) {
    def foo() = {
      bar(this.asInstanceOf[Parent].changedParam)
    }
  }
}

2) Using early initializers syntax:

class Parent(protected val changedParam: MyClass)

class Child(changedParam: MyClass) extends {
  val superChangedParam = doStuff(changedParam)
} with Parent(superChangedParam) {
  def foo() = {
    bar(superChangedParam)
  }
}

Nevertheless the best solution is to give a different name to your param.



标签: scala