13

I am confused why the following pattern's call to the other constructor block does not count as the first statement in the constructor block:

public class MyClass
{
    final String someProperty;
    public MyClass()
    {
        /* some default logic */
    }
    public MyClass(String someProperty)
    {
        this().someProperty = someProperty;
    }
}

It seems to me like the order of operations should be:

  1. The empty constructor runs.
  2. The property gets assigned.

However, I get a compile error:

Call to another constructor must be the first statement in constructor block

Edits

I realize that I can just separate the calls.

public MyClass(String someProperty)
{
    this();
    this.someProperty = someProperty;
}

However, it sure would be nice to be able to one-line this constructor chaining. I am trying to understand why this() behaves differently than new MyClass(). They both run the empty constructor! But they allow for different behavior after the constructor completes. Note that I can chain directly off the constructor when using the fluent pattern.

new MyClass().doSomeStuff().doOtherStuff();
6
  • 4
    why aren't you doing this.someProperty = property; in the second constructor? – cropredy Jan 22 '17 at 1:56
  • 1
    Constructor has no return type. So this() won't return anything. Thus when u try to access it's property it wud give error – Pranay Jaiswal Jan 22 '17 at 2:14
  • Because the called constrictor also has logic I need. And it should return itself. – Adrian Larson Jan 22 '17 at 2:17
  • You should try this in ur second parameterized constructor. This(); this.someproperty=property. – Pranay Jaiswal Jan 22 '17 at 2:18
  • 1
    You're piqued my interest. Challenge accepted. – sfdcfox Jan 22 '17 at 2:46
9

I think this speaks somewhat to Apex inheriting so much from Java.

Try the same thing with a Java compiler:

public class HelloDog
{
  public static void main(String[] args)
  {
    Dog myDog = new Dog();
    System.out.print(myDog);
  }
}

class Dog{
    protected String name;

    public Dog(){
        this("Fido").name="Arf"; 
        //this.name="Arf";
        System.out.println("Inside no argument constructor of Dog class");
    }

    public Dog(String name){
        this.name = name;
        System.out.println("One arg constructor of Dog class");
    }
}

Gives the following compiler errors:

/HelloDog.java:15: error: call to this must be first statement in constructor

this("Fido").name="Arf";
. ^

/HelloDog.java:15: error: void cannot be dereferenced
this("Fido").name="Arf";
. ^

As Martin answered, the chained constructor returns void rather than the instance, so you can't chain anything to it.

Calling the constructor and constructor chaining are two different things with very similar syntax.

Personally I think the C# syntax makes constructor chaining clearer. Rather than being a special case of this that appears at the start of the constructor it has it's own explicit syntax:

class Dog {
    protected String name;

    public Dog() : this("Fido")
    {
    }

    public Dog(String name){
        this.name = name;
    }
}

If you tried to do a public Dog() : this("Fido").name="arf" { in C# the compiler will look at you funny and ask where the missing { is. The constructor chaining here isn't a statement that can end in a semicolon. It's telling the compiler to chain the constructors together.

5
  • I'm still not convinced the constructor returns void. If you call the constructor outside of the class, it returns an instance of that class. There is no "return type", but it implicitly returns itself. – Adrian Larson Jan 23 '17 at 0:05
  • That's just the thing. Syntactically you aren't calling "the constructor". To do that you would need to use the new keyword. Instead you are performing constructor chaining. Yes it looks pretty much the same, but they are doing different things. I might see if a fine debug log will show the difference. – Daniel Ballinger Jan 23 '17 at 0:38
  • Very interesting. If that's not calling the constructor...the error message is even more confusing. It's all kind of mind bending. – Adrian Larson Jan 23 '17 at 1:20
  • Just think of it as calling a chained constructor that happens to use another constructors code. If calling this(); in the first line was really behaving like a constructor then it would be allocating another object on the heap, which is clear is not. Someone with internal knowledge of the Apex bytecode and interpreter could probably explain it better. – Daniel Ballinger Jan 23 '17 at 1:33
  • It just seems like it would make some uses of polymorphism a lot more elegant. I wonder if we might get that someone to wander by and take a look at this question. Stranger things have happened on SFSE. – Adrian Larson Jan 24 '17 at 1:34
12

I believe you should be doing it this way:

public class MyClass
{
    final String someProperty;
    public MyClass()
    {
        /* some default logic */
    }
    public MyClass(String someProperty)
    {
        this();
        this.someProperty = someProperty;
    }
}

Call the constructor for the object and set the property on the object separately.

The reason you can't chain a constructor call with another method is that the constructor call doesn't have a return type.

If you had a different instance method on the object that returned this, you would be able to chain those two method calls together.

public class MyClass
{
    String someProperty;
    public MyClass returnThis(){
        return this;
    }
    public MyClass(String someProperty)
    {
        returnThis().someProperty = someProperty;
    }
}

this() behaves differently than new MyClass() because the new operator returns the reference to the object created. When doing constructor chaining with this(), it just runs the other constructor subroutine, and doesn't return a reference to the object being constructed. You can't do this().someProperty just like you can't chain methods that return void: new list<String>().add('s').add('t');

2
  • Perfectly sums up my comments. – Pranay Jaiswal Jan 22 '17 at 2:25
  • I know I can do it that way. My question is why I can't combine them. – Adrian Larson Jan 22 '17 at 2:36
7

Summary: Your initial assumption was (kind of) wrong. Which led to some interesting findings. Read on.

Consider the following code:

class DoNothing { Integer value; }
Integer getInt() {
    return 5 / 0;
}
Object o;
((DoNothing)o).value = getInt();

We have a pending NullPointerException and a pending MathException. Which one happens first? In actuality, the MathException happens first. More specifically, your assumption about the order of operations was incorrect.

Let's get to a basic example of where that could lead to a surprise:

class A() {
  String value;
  A() {
    this('Hello').value = value + value;
  }
  A(String v) {
    value = v;
  }
}

What should value be? "HelloHello"? Nope, it would be "nullnull" (assuming this code were allowed to compile). That's because the right-hand was evaluated first, cached, then the left-side was evaluated, and then the concatenation/assignment operator was applied using the null value. We were depending on value being evaluated later, but it was not.

(Edit: I noticed that += actually does work correctly. Adding demos.)

Here's an example of the caching effect in play:

public class exampleValueSet {
    public String value;
    public exampleValueSet setValue(String v) {
        value = v;
        return this;
    }
}
exampleValueSet v = new exampleValueSet();
v.setValue('world').value = v.value + v.value;
system.debug(v.value);

While using += results in the intended result:

public class exampleValueSet {
    public String value;
    public exampleValueSet setValue(String v) {
        value = v;
        return this;
    }
}
exampleValueSet v = new exampleValueSet();
v.setValue('world').value += v.value;
system.debug(v.value);

Note that the Order of Operations manual specifically says that the assignment operators evaluate last. This can cause the timing for when a value is evaluated to change. For example += v.value + v.value yields a different result than += v.value. Unless you're 100% certain of how side effects will occur, it's best to avoid trying to introduce them to your code. It gets pretty complicated quickly.

Worse, if they allowed code like this, and later decided to change the order of operations, then code that depended on the current order might break. To avoid any ambiguity, they just blocked it altogether.

It's a very powerful block, too. It supersedes all other errors I tested it against:

Unary increment/decrement can only be applied to numeric expressions

this(5)++;

Arithmetic expressions must use numeric arguments

this() / 5;

Comparison arguments must be compatible types

this() == 5;

Expression cannot be a statement

this() == this;

Object has no superclass for super invocation

super() == this;

All of those errors, in any other context, are replaced by the same Call to another constructor must be the first statement in constructor block error when applied to this() or super(). It even beats out super()'s "no superclass" error; this error is literally thrown even before types are checked or anything else.


Also, interestingly, they've specifically blocked using parameters that lead to instance methods or variables. So, you can't do this either:

public class A {
  public Integer b;
  public A() {
    this(b=5);
  }
  public A(Integer v) {
    b = v * 2;
  }
}

Which would normally be valid code if called on a normal method (5 would be assigned to b, and then 5 would be multiplied by 2, leaving b as 10).

In that case, we get a different error:

cannot reference instance variables or instance methods inside a constructor invocation

Unless, of course, you cause the "Call to another constructor..." error, as before.

2
  • You definitely understood what I'm getting at. I still don't understand, though. Why does it work when calling the constructor externally but not internally? – Adrian Larson Jan 22 '17 at 14:30
  • 1
    @AdrianLarson Some of my examples are kind of weak, but the general point is that you may end up accessing an uninitialized object when you're toying around with the constructor call chain. In order to avoid any unfortunate side effects, they've just globally disabled the ability to do so. I can't confirm this directly, but I suspect that internally, some of the memory might not even be allocated until the initial call chain has completed. – sfdcfox Jan 22 '17 at 17:23

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.