3

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

I found out that print(i) prints the constructor parameter i of Subclass(i : Int)

Now, my question is: in a situation like this, how do I access the field i of Superclass?

Mario Galic
  • 47,285
  • 6
  • 56
  • 98
mizanshu
  • 45
  • 4
  • 1
    You can use `this.i`, I think. If you have any references to a constructor parameter, it gets turned into a field or something like that (even though you can't access it outside). – user Jul 01 '20 at 17:38
  • @user doesn't work :/. this.i also returns the constructor paramter – mizanshu Jul 01 '20 at 17:40
  • 1
    Yes, I just noticed that. Relevant discussion on the old scala website: https://www.scala-lang.org/old/node/968 – user Jul 01 '20 at 17:47
  • https://www.reddit.com/r/scala/comments/hjg7cg/bug_with_inheritance_and_default_constructor/ – Dmytro Mitin Jul 02 '20 at 19:25

2 Answers2

6

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
Mario Galic
  • 47,285
  • 6
  • 56
  • 98
3

@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

Dmytro Mitin
  • 48,194
  • 3
  • 28
  • 66