Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is my field null even though it should be instantiated immediately?

The fields I am declaring and immediately instantiating are null. Here is a sample code:

public class NullFieldSSCCE {

    static abstract class Parent {
        List<String> values;
        Parent() {
            values = getValues();
        }

        protected abstract List<String> getValues();   
    }

    static class Child extends Parent {

        String param1="test1";
        String param2="test2";

        Child() {
        }

        @Override
        protected List<String> getValues() {
            return Arrays.asList( new String[] {param1, param2} );
        }
    }

    public static void main(String[] args) {
        Child child = new Child();

        System.out.println("Child p1="+child.values.get(0)+", p2="+child.values.get(1));
    }

}

The result of running this is

Child p1=null, p2=null

while I would expect

Child p1=test1, p2=test2

How is this possible? These fields are instantiated at the same moment the class is, aren't they?

like image 631
Dariusz Avatar asked Mar 21 '23 23:03

Dariusz


1 Answers

What is happening

What you have encountered is described in JLS in chapters 8.3.2 and 8.8. You can find some less detailed and perhaps easier to read information here.

The issue here is the order of initialization of an object.

Here is the order of initialization for your example:

  1. Parent fields are instantiated: values becomes an instance of ArrayList
  2. Child constructor calls super(), hence invoking Parent constructor
  3. getValues() is called; in this case, it is Child.getValues();
  4. Child fields are instantiated: param1 and param2 have their values assigned
  5. Child constructor continues - and does nothing more in your case

You assumend that all fields from all the classes in given hierarchy are instantiated at the same time, that 4 happens before 3 and roughly at the same time as 1. This assumption is, unfortunately, wrong.

Learning the initialization order

Even if you have read the links I have given you, you may not be certain how things will work in a specific case. Whenever you have doubts about order of initialization do not hesitate to add printfs to check it. I've done it for you this time:

public class NullFieldSSCCE {

    static abstract class Parent {
        List<String> values = new ArrayList<String>() {{
          System.out.println("Parent.values instantiation");  
        }};

        Parent() {
            System.out.println("Parent()");
            values.addAll(getValues());
        }

        protected abstract List<String> getValues();   
    }

    static class Child extends Parent {

        String param1="test1";
        String param2="test2";
        Object param3 = new Object() {{System.out.println("Child.param3 instantiation"); }};

        Child() {
            System.out.println("Child()");
        }

        @Override
        protected List<String> getValues() {
            System.out.println("Child.getValues()");
            return Arrays.asList( new String[] {param1, param2} );
        }
    }

    public static void main(String[] args) {
        System.out.println("start");
        Child child = new Child();

        System.out.println("Child p1="+child.values.get(0)+", p2="+child.values.get(1));
    }

}

And the output is:

start
Parent.values instantiation
Parent()
Child.getValues()
Child.param3 instantiation
Child()
Child p1=null, p2=null
like image 194
Dariusz Avatar answered Apr 06 '23 01:04

Dariusz