Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to call superclass constructor from child class in scala and how to do constructor chaining

Tags:

scala

I was trying below java code to convert it in scala but i was not able to call superclass constructor from child class in scala. How do i solve this problem.

//java code
class A
{
    public A(String a){}
    public A(String a, int b){}
}
class B extends A
{
    public B(String c){
        super(c);
    }
    public B(String c,int d){
        super(c,d);//how to do this in scala
    }
}
like image 304
J.Doe Avatar asked Jul 02 '16 13:07

J.Doe


1 Answers

You would benefit from some basic scala tutorial on constructors but let me try. In short -- you cannot do this in scala exactly.

  1. Unlike in Java, in Scala there is a single 'primary' constructor and all other constructors must eventually chain to it. If your primary constructor is defined as class Foo(x: Int, y: Int, s: String) extends Bar(s) you could define another constructor in Foo but it must chain back to its main constructor as such def this(x: Int) = this(x, 0, "")
  2. In Scala, the way you express calling the super constructor is in the extends Bar(...) portion of your class definition (and so you can only do that that once as part of defining your primary constructor and class). Using the same example as above, if you have class Bar(s: String) and you extend it class Foo(val x: Int, val y: Int, s: String) extends Bar(s) you are invoking the super constructor at the same time as you are declaring that you are extending it, ie the extends Bar(s) part.

So given those two points above, since all constructors must chain to the single primary constructor of a class AND that single primary constructor only invokes a single super constructor, you cannot exactly translate your Java example in Scala. However, you can get something equivalent by doing this.

final class Foo(x: Int, y: Int, s: String) extends Bar(s) {
     def this(x: Int) = this(x, 1/*Foo's default*/, ???/*you likely want the same default that super would use, in this case "bar", see below*/)
}

class Bar(s: String) {
     def this() = this("bar")
}

In effect, in scala you chain first, applying all defaults as you go, and then you call the super rather than calling super from your various constructor versions as you can in Java.

You COULD store the super constructor default value in a companion object Bar { defaultString: String = "bar" } and use that both in Foo and in Bar to ensure that the impl remain in sync. This seems clunky but is actually safer because it ensures identical behavior regardless of which constructor you invoke, while in java you might have different defaults (which would be questionable). Here it is:

final class Foo(x: Int, y: Int, s: String) extends Bar(s) {
     def this(x: Int) = this(x, 1/*Foo's default*/, Bar.defaultString)
}

class Bar(s: String) {
     def this() = this(Bar.defaultString)
}
object Bar {
    protected val defaultString: String = "bar"
}

However note that in scala -- because of the well established facility of companion objects and apply methods (you can think of them as static factory methods in Java), I do not see people using a lot of non-primary constructors. They use several apply methods instead so your primary constructor generally takes all possible parameters. Another option is to have them use default values. So you very rarely need non-primary constructors and chaining.

On a related note, if you have constructor parameters that you are passing directly to your super class, be careful not to include val as that would duplicate the variable in the subclass as well, and increase your memory footprint. It is kind of subtle. Additionally, if you happen to refer to a variable that does NOT have val in front of it in your primary ctor, but is not visible from the super class, the compiler will assume it has a private[this] val modifier and will also save it as a field in the sub class. Let me illustrate

class A(val name: String, val counter: Int)

class B(
   val b: Int /*this is B's field by design*/,
   str: String/*meant to be passed to super, so no val*/,
   i: Int /*meant to be passed to super, so no val*/) extends A(str, i) {

   def firstLetterOfName = str.head // Oops, I should have done name.head
}

Since I referred to str which appears in B's constructor rather than name which is A's field, str will get a default private[this] val modifier and will be saved as B's field as well, thus duplicating the name field from the super class A. Now we have two fields that are duplicative.

like image 198
Creos Avatar answered Oct 15 '22 18:10

Creos