1

I have my own class that extends an external class that overrides an external class to make its text always "Hello". It looks like:

import ext.ExternalClassA;

public class ClassB extends ExternalClassA {
    private String customText = "Hello";

    public ClassB() { // line 6
    }

    @Override
    public void setText(String newText) {
        return;
    }

    @Override
    public String getText() {
        return customText;
    }
}

However, when I run my program, it crashes with an error like:

Exception in thread "main" java.lang.NullPointerException
    at ext.ExternalClassA.setup(ExternalClassA.java:123)
    at ext.ExternalClassA.<init>(ExternalClassA.java:45)
    at my.ClassB.<init>(ClassB.java:6)

When I look at ExternalClassA.java, I see this:

// imports...

public class ExternalClassA {
    private String text;
    // Other variables

    public ClassB() {
        text = "Default";
        setup(); // line 45
    }

    // more code...

    private void setup() {
        if (text == null)
            return;
        String textStr = getText().toString(); // line 123

        // more code...
    }

    // more code...
}

What am I doing wrong? How can I fix it? Is it a problem with the external class?

Ky -
  • 30,724
  • 51
  • 192
  • 308
  • I just ran into this when trying to override Swing methods. A couple searches on SO turned up no similar questions, but if you know of one, please link it in the comments so I can move my answer there. – Ky - May 26 '15 at 18:56
  • 1
    Related: http://stackoverflow.com/questions/3404301/whats-wrong-with-overridable-method-calls-in-constructors – Sotirios Delimanolis May 26 '15 at 19:00
  • Can you point us to the Swing class involved? – Sotirios Delimanolis May 26 '15 at 19:10
  • @SotiriosDelimanolis `javax.swing.text.JTextComponent` (particularly setText(String), on line 1672 in Java 1.8.0_25) – Ky - May 26 '15 at 19:11

2 Answers2

1

The problem is that ExternalClassA is attempting to use its getText() method to get text. As far as it sees, from the previous couple lines, this should be safe. However, unbeknownst to it, your override of getText() does not always return text. This may seem fine to you, since you use customText, which is set to "Hello". However! This assignment does not happen until the superclass' initializer exits. Here is the order of initialization:

Only once:

  1. superclass' static variables
  2. superclass' static constructor
  3. this' static variables
  4. this' static constructor

Every new object:

  1. superclass' variables
  2. superclass' constructor
  3. this' variables
  4. this' constructor

So, when the superclass calls getText(), it goes into yours. In yours, at this moment, customText is null, but superText is "Default".

This may seem like a no-win, since there's no way to set your value before the superclass is done initializing, but there is a solution! It lies within this same behavior. The same Java mechanic that defaults customText to null also defaults primitives to 0 or false. We can use this and modify ClassB like such:

import ext.ExternalClassA;

public class ClassB extends ExternalClassA {
    private String customText = "Hello";
    private boolean hasStarted = true;

    public ClassB() {
    }

    @Override
    public void setText(String newText) {
        return;
    }

    @Override
    public String getText() {
        if (!hasStarted)
            return super.getText();
        return customText;
    }
}

Note that hasStarted will be true ONLY after the superclass' constructor has returned.

tl;dr


What am I doing wrong?

You didn't know that the order-of-operations in initialization causes your string to be null until your constructor has control.

How can I fix it?

Fallback to superclass methods during super initialization.

Is it a problem with the external class?

Yes, the superclass should not check a variable to assume the return value of a method.

Ky -
  • 30,724
  • 51
  • 192
  • 308
0

You should override setup next way:

public void setup() {
    setText("Hello");
    super.setup();
}

After that you may drop all your stuff from ClassB.

Zefick
  • 2,014
  • 15
  • 19
  • On your last line, then what happens when someone calls `setText("a string")` on a `ClassB` object? – Ky - May 26 '15 at 19:12