Java property values loaded in constructor are being reverted after the constructor?

I'm noticing some strange behavior. I have the following classes:

public abstract class BaseFoo
{
   public BaseFoo(String key)
   {
       Data data = Something.load( key );
       load( data );
   }

   public abstract void load(Data data);
}


public class Foo extends BaseFoo
{
   @Expose public long id = 0;
   @Expose public String name =  "";
   //...

   public Foo(String key)
   {
      super(key);
   } 

   @Override
   public void load(Data data)
   {
     this.id = data.id;
     this.name = data.name;
     //snip setting misc other fields
   }
}

Now, if I do the following:

Foo f = new Foo ( "abcd" );

Then I expect f.id to contain the id of the Foo record which was loaded. However, its value is actually 0. By running this code through a debugger, I've found that Foo.load() is called before the public long id = 0 line is executed. So, although load() is called and it does set id and other fields to their correct values, those values are then overwritten by the public long id = 0; and other variable declarations..

I've never come across this issue before, usually the values set in a constructor overwrite the default values in the variable declaration. Is it because I'm calling load through super that the values are being overwritten? If so, is there a convenient fix for this?

Jon Skeet
people
quotationmark

This is the problem with calling virtual methods in a constructor...

The order of execution is:

  • BaseFoo variable initializers
  • BaseFoo constructor body
  • Foo variable initializers
  • Foo constructor body

That behaviour is well documented in the JLS, section 12.5.

So actually, if you change these:

@Expose public long id = 0;
@Expose public long name =  "";

to

@Expose public long id;
@Expose public String name;

and then conditionally set name to "" if it's not already non-null by the time you get to the Foo constructor body, then I think you'll be okay.

However, I'd strongly advise you to approach this with a different design. Virtual method calls in constructors get really messy really quickly.

people

See more on this question at Stackoverflow