Disambiguate constructor parameter with same name as class field of superclass

前端 未结 2 1382
佛祖请我去吃肉
佛祖请我去吃肉 2020-12-21 10:10

When playing around with Scala I had a code like this:

class Superclass(var i : Int){}

class Subclass(i : Int) extends Superclass(0) {
   print(i)
}
<         


        
相关标签:
2条回答
  • 2020-12-21 10:36

    Type ascription can up-cast the type of this which effectively disambiguates the two identifiers

    class Subclass(i : Int) extends Superclass(0) {
      print((this: Superclass).i)
      print(i)
    }
    

    As a side-note, there also exists the following syntax that could be used in the case of method members (and which perhaps is not well-known)

    super[ClassQualifier]
    

    For example, consider the following

    trait A {
      def f = "A"
    }
    
    trait B extends A {
      override def f = "B"
    }
    
    class C extends A with B {
      println(super[A].f)
      println(super[B].f)
      println(f)
      
      override def f = "C"
    }
    
    new C
    // A
    // B
    // C
    
    0 讨论(0)
  • 2020-12-21 10:42

    @MarioGalic answered this question. I'll just make some additions that are too long for comments.

    Common misunderstanding is that i in

    class MyClass(i : Int)
    

    is just a constructor parameter and not a field. Actually if we do

    import scala.reflect.runtime.universe._
    println(reify{
      class MyClass(i : Int)
    }.tree) 
    

    we'll see (Scala 2.13.2)

    {
      class MyClass extends AnyRef {
        <paramaccessor> private[this] val i: Int = _;
        def <init>(i: Int) = {
          super.<init>();
          ()
        }
      };
      ()
    }
    

    So a parameter of primary constructor without val/var generates private[this] field. So

    class MyClass(i : Int)
    

    is similar to

    class MyClass(private[this] val i : Int)
    

    and NOT similar to Java's

    public class MyClass { 
      public MyClass(int i) {
      }
    }
    

    without fields.

    We can check that i is a field referring to it with this inside class body

    class MyClass(i : Int) {
      println(this.i)
    }
    new MyClass(1) // prints 1
    

    The field i is private[this] so we can't refer it outside the class body (or inside the body on instance different than this)

    class MyClass(i : Int) {
      //println(new MyClass(2).i) //doesn't compile
    }
    //new MyClass(1).i //doesn't compile
    

    I didn't find proper place in Scala specification but such behavior is well-known for a long time. For example in "Scala for the impatient" by Cay S. Horstmann it's written (edition 2, section 5.7):

    Construction parameters can also be regular method parameters, without val or var. How these parameters are processed depends on their usage inside the class.

    • If a parameter without val or var is used inside at least one method, it becomes a field. For example,

       class Person(name: String, age: Int) {  
         def description = name + " is " + age + " years old"
       }
      

      declares and initializes immutable fields name and age that are object-private. Such a field is the equivalent of a private[this] val field (see Section 5.4,“Object-Private Fields,” on page 56).

    • Otherwise, the parameter is not saved as a field. It’s just a regular parameter that can be accessed in the code of the primary constructor. (Strictly speaking, this is an implementation-specific optimization.)

    Actually in 2.13.2 I can't confirm the second case.


    Now let's have two classes.

    Scala doesn't allow

    class Superclass {
      val i: Int = 1
    }
    
    class Subclass extends Superclass {
      //val i: Int = 2 //doesn't compile
    }
    

    unless we add override

    class Superclass {
      val i: Int = 1
    }
    
    class Subclass extends Superclass {
      override val i: Int = 2
    }
    

    But if Superclass's field is private[this] everything is ok without override

    class Superclass {
      private[this] val i: Int = 1
    }
    
    class Subclass extends Superclass {
      val i: Int = 2
    }
    

    Actually if we try to add override this will not compile.

    The reason is this being not overriding. One of fields is private[this] i.e. not accessible outside the object where the field is defined in, so these are just two different fields:

    class Superclass {
      private[this] val i: Int = 1
    }
    
    class Subclass extends Superclass {
      val i: Int = 2
    
      println(this.i) // or just println(i)
    // println((this: Superclass).i) //doesn't compile
    }
    
    new Subclass
    //2
    

    or

    class Superclass {
      val i: Int = 1
    }
    
    class Subclass extends Superclass {
      private[this] val i: Int = 2
    
      println(this.i) // or just println(i)
      println((this: Superclass).i)
    }
    
    new Subclass
    //2
    //1
    

    So in our case

    class Superclass(var i : Int)
    
    class Subclass(i : Int) extends Superclass(0)
    

    are like

    class Superclass extends AnyRef {
      var i: Int = _
      def this(_i: Int) = {
        super() //pseudocode
        i = _i
      }
    }
    
    class Subclass extends Superclass {
      private[this] val i: Int = _ //pseudocode
      def this(_i: Int) = {
        super(0) //pseudocode
        i = _i  //pseudocode because "i" is a val -- well, compiler can do things that we can't do in source code
      }
    }
    

    Inside Subclass this.i or just i refers to Subclass's field private[this] val i: Int and (this: Superclass).i refers to Superclass's field var i: Int.


    Do scala constructor parameters default to private val?

    Scala Constructor Parameters

    https://www.scala-lang.org/old/node/8384.html

    0 讨论(0)
提交回复
热议问题