1

I've distilled a real-world issue down to an example to show my question:

public class SubclassTrial {

  virtual class BaseClass {
    public string label { get; set;}
    public integer value { get; set;}

    public BaseClass(string pLabel) {
      this.label = 'B1: ' + pLabel;
      this.value = 0;
    }

    public BaseClass(string pLabel, integer pValue) {
      this('B2: ' + pLabel);
      this.value = pValue;
    }        
  }

  class SubClass extends BaseClass {
    public SubClass(string pLabel) {
      super('S1: ' + pLabel);
    }

    public SubClass(string pLabel, integer pValue) {
      super('S2: ' + pLabel, pValue);
    }
  }

  public static void testIt() {
    SubClass s = new Subclass('test', 2);
    system.debug(s.label);
    system.assertEquals('B1: S1: B2: S2: test', s.label);
  }

}

When TestIt() runs, it creates a SubClass using the 2-argument constructor, which uses super to call BaseClass's 2-argument constructor. That in turn calls this(string) - a 1-argument constructor for the calling class. I would expect that to invoke SubClass(string) due to polymorphism, not BaseClass(string), since the actual class instance is a SubClass. But that isn't happening; my assertion fails:

System.AssertException: Assertion Failed: 
  Expected: B1: S1: B2: S2: test, 
    Actual: B1: B2: S2: test

The Apex docs don't really cover the intricacies of overridden constructors too well, so I haven't been able to find any concrete explanation. Why is this(string) calling the BaseClass version instead of the Subclass version when the calling class is a SubClass?

EDIT to clarify: a couple of answers have suggested I'm wrong in my assumptions; and in particular @sfdcfox states, "There is no way, during construction, for the parent class to call any method of the child's class, including other child constructors, virtual, or abstract methods." This is not my experience, which is why I expected the other behavior. I've expanded my trial class to show this, by adding otherLabel, which is set by a virtual method. If you run this, you will see that the first assertion (of otherLabel) passes, and only the second assertion (label) fails. Specifically, the BaseClass constructor calls this.setOtherLabel(), which invokes SubClass.setOtherLabel(). I expected this() to follow the same pattern.

public class SubclassTrial2 {

  virtual class BaseClass {
    public string label { get; set;}
    public string otherLabel { get; set;}
    public integer value { get; set;}

    public BaseClass(string pLabel) {
      this.label = 'B1: ' + pLabel;
      this.setOtherLabel(pLabel);
      this.value = 0;
    }

    public BaseClass(string pLabel, integer pValue) {
      this('B2: ' + pLabel);
      this.value = pValue;
    }        

    public virtual void setOtherLabel(string pOtherLabel) {
      this.otherLabel = 'B: ' + pOtherLabel;
    }
  }

  class SubClass extends BaseClass {
    public SubClass(string pLabel) {
      super('S1: ' + pLabel);
    }

    public SubClass(string pLabel, integer pValue) {
      super('S2: ' + pLabel, pValue);
    }

    public override void setOtherLabel(string pOtherLabel) {
      this.otherLabel = 'S: ' + pOtherLabel;
    }
  }

  public static void testIt() {
    SubClass s = new Subclass('test', 2);
    system.debug(s.label);
    system.debug(s.otherLabel);
    system.assertEquals('S: B2: S2: test', s.otherLabel);
    system.assertEquals('B1: S1: B2: S2: test', s.label);
  }

}

2 Answers 2

3

You're not meant to interact with the child before all constructors that will be called have been called. This is why you can't call this or super constructors after the first line of code in a constructor. For example, this is not allowed:

public SubClass(string pLabel) {
  System.debug('Entering subclass...');
  super('S1: ' + pLabel);
}

For this first line of code, the constructor chain, this always refers to the current class, and super always refers to the parent class. Also, for this specific instance, instance methods are also not allowed:

public SubClass(string pLabel) {
  // CompileFail: cannot call instance methods in a constructor invocation
  super('S1: ' + setLabel(pLabel)); 
}

After this first line of execution, you're allowed to access virtual and abstract methods normally, typically to get some specific configuration information about the subclass.

The intent here is that the base class should be initialized first, followed by each subclass in order down to the class that is actually being constructed. This design is inherited from Java. As far as I know, there's no documentation that really calls this out, presumably because the documentation teams presumes that most people who read the documentation are familiar with Java.

2
  • This makes sense, and I was aware of the "first line" requirement fall calling this()/super(). The documentation is the real issue here. Inheritance of constructors is a low-enough level detail that you shouldn't expect all Apex developers to know how Java handles such details. Commented Sep 25, 2018 at 20:18
  • @JasonClark I agree. You'll find lots of examples of how the documentation does this if you go looking for them.
    – sfdcfox
    Commented Sep 25, 2018 at 20:27
1

This is working exactly as I would expect it to but I suppose I'm not as Object Oriented as others.

this is inside the constructor for BaseClass and as such this refers to BaseClass and not SubClass.

I would think that if this was Javascript it might work the way you are expecting as this in js is very different from this in most c based languages. In Javascript this is much more contextual and is based more on the calling context than the defining context.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .