4

One of my classes inherits from a class in a framework I use. The superclass calls a method in its constructor which I overwrite in my own class. The method uses a field I want to initialize before it is called by the super constructor to avoid a NullPointerException.

Is there any way to do this?

Here is a synthetic test scenario, I want c in Child to not be null when call is called.

public class Test {

    public static class Parent {
        public Parent() {
            super();
            call();
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Parent");
        }
    }

    public static class Child extends Parent {

        private Child c = this;

        public Child() {
            super();
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Child, c is " + (c == null ? "null" : "this"));
        }
    }

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

Prior to Java 7, that was possible. I could get by with stunts like this:

    public static class Child extends Parent {

        private Child c;

        private Child(Object unused) {
            super();
        }

        public Child() {
            this(c = this);
        }

        // only called from parent constructor
        public void call() {
            System.out.println("Child, c is " + (c == null ? "null" : "this"));
        }
    }

Now, that won't work anymore. I appreciate the additional safety, but the call from super destroys any safety gained by it and reduces the flexibility.

I'd like a way to circumvent this restriction. As an alternative, I'd like to know what's gained by an restriction that spares the super constructor case.

Arne
  • 1,884
  • 1
  • 15
  • 19
  • You can't initialize your field before the super call. However there is a (very) ugly hack. You can do in your `Child` class `public void call() { c= this; System.out.println("Child, c is " + (c == null ? "null" : "this")); }` – user2336315 Dec 03 '13 at 09:56
  • 5
    The real problem here is the antipattern of calling an overridden method from the base class constructor. Don't do that. – user207421 Dec 03 '13 at 10:21
  • BTW: This was never possible in Java, also not before Java 7. Java is down-wards compatible. If it compiles in Java 6 it will compile in Java 7. – Thomas Uhrig Dec 03 '13 at 10:28
  • @EJP yes, it is an antipattern. But if I have to use it with a framework and I am not able to change the base class - what can I do? – Arne Dec 03 '13 at 10:46
  • @user2336315 that's pretty much what I do right now: `if (c == null) { c = ... } ...`. But I don't see why I should have to. – Arne Dec 03 '13 at 10:47
  • @Arne I think it's basically Java's way of warning you that your object isn't ready yet. – Adrian Mouat Dec 03 '13 at 11:00
  • possible duplicate of [Why does this() and super() have to be the first statement in a constructor?](http://stackoverflow.com/questions/1168345/why-does-this-and-super-have-to-be-the-first-statement-in-a-constructor) – Raedwald Dec 03 '13 at 12:52
  • @Raedwald yes, the two questions are related. But the other one is from before Java 1.7, the restriction I'm facing here didn't apply then. – Arne Dec 03 '13 at 13:38
  • @Arne Can you come up with a simple example that works in Java 6 but not 7? I'm using 6 and get the behaviour you describe above for Java 7 – Adrian Mouat Dec 03 '13 at 16:30
  • @AdrianMouat I had an instrumentation with asm setting the variable before super was called. The bytecode loaded without issues in Java 6 but does no longer in Java 7. I got there by compiling and decompiling a lot of different examples, but I can't reproduce it any longer. If the code above doesn't work, maybe it was by setting it in a method call which was called in the constructor. Worst case would be I coded it by hand and wrongly remember it to be based on an example. The way this is going, I tend to think that's the case. Thanks for the help and sorry for the bother :( – Arne Dec 03 '13 at 16:46
  • 1
    @Arne: if you use instrumentation you can create byte code to initialize *`final`* fields before super constructor call, iirc. In your code example above, the field is not `final`. Maybe you once encountered a verifier being too relaxed about it… – Holger Dec 03 '13 at 19:09
  • @Holger it's not final - and it must not be. That's the whole point of the excercise in this case. Still, good to know that. – Arne Dec 03 '13 at 22:54

4 Answers4

3

A static initializer will be called before the super class constructor. However, you won't be able to set any non-static fields, so it most likely won't help.

http://docs.oracle.com/javase/tutorial/java/javaOO/initial.html

A non-static initialization block also doesn't help as it is called after the super class constructor completes.

Another approach may be to do nothing when called from the super-constructor and make the call again the child constructor, e.g:

    public Child() {
        super();
        call();
    }

    public void call() {

       if (c==null) {
         return;
       }

       System.out.println("do something with c now");

    }

This won't work if more stuff happens in the super constructor that is dependent on this method though.

I have to agree with EJP that this is all a bad idea; it would be much better to find a completely different solution that doesn't involve torturing constructors.

Adrian Mouat
  • 44,585
  • 16
  • 110
  • 102
  • 1
    Processing non-static initializer blocks and processing variable initializers happens during the same stage of class instance creation. See step 4 in [12.5. Creation of New Class Instances](http://docs.oracle.com/javase/specs/jls/se7/html/jls-12.html#jls-12.5) – Patricia Shanahan Dec 03 '13 at 10:18
  • That's what I do right now. I'd love to do it differently, though... and I wholeheartedly agree with EJP. – Arne Dec 03 '13 at 10:50
  • @Patricia Sorry, I was in the middle of editing when you made your comment. That's a good link though. – Adrian Mouat Dec 03 '13 at 11:02
2

Note that your class Child is translated into the following equivalent by the Java compiler:

public static class Child extends Parent {

    private Child c;

    public Child() {
        super();
        c = this;
    }

    // Remaining implementation
}

This is the same for Java 6 and 7, the generated byte code for the constructor is even the same when compiling with any of both versions. The local field is always instantiated after calling the super constructor. What compiler did you use to make your "work-around" work?

This restriction is quite elementary. This way, you can rely on that super constructors are applied first. Imagine, your sub constructor was using a final field declared in this class. You could not guarantee that this field was initialized if you would not guarantee this constructor execution order. This restriction makes Java more reliable.

Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
2

This is an answer to the "I'd like to know what's gained by an restriction that spares the super constructor case." part of the question.

In the course of construction, there are three states the fields declared in a class X might be in: All default values, all fully initialized to consistent working values, and anything else.

The objective seems to be that code in classes other than X only sees one of the first two states. When non-static initializer or constructor code for any of X's superclasses is running, X's fields are all in the default state. When non-static initializer or constructor code for any subclass of X is running, all X's fields have been initialized to a fully consistent, usable state.

Only X initializer and constructor code should have to deal with X fields in an inconsistent state, some initialized, some default, and some partially initialized.

This can certainly be circumvented by calling X methods from an X superclass initializer or constructor, but that is commonly regarded as an anti-pattern. The problem is running X code that is not called locally from an initializer or constructor in a partially constructed X. If that code changes a field, the change may be overwritten when the X initializers and constructor body run.

Patricia Shanahan
  • 25,849
  • 4
  • 38
  • 75
2

This should never have worked in the first place.

Note that at the bytecode level, this actually is allowed. In bytecode, you can set fields declared in the current class before calling a superclass constructor. However, Java provides no way to use this behavior. It is only used secretly by the Java compiler to initialize the synthetic fields added to support inner classes.

Antimony
  • 37,781
  • 10
  • 100
  • 107
  • Can I still do this in Java 7 - and how? I should probably add this scenario came up in instrumented code, it's a corner case I want to support. – Arne Dec 03 '13 at 16:22
  • What do you mean by still do it? As mentioned before, it should have never worked in the first place. – Antimony Dec 03 '13 at 20:56