MudGuard: Methodenaufruf VOR Initialisierung ==> NullPointerException

Hi,

heute hat mich Java mal wieder überrascht.
Bisher ging ich davon aus, daß die Initialisierung von member-Variablen geschieht, bevor eine Methode des Objekts aufgerufen werden kann.

Aber das scheint wohl doch nicht der Fall zu sein.

Ich hab eine Klasse RecordFilterGUI von JPanel abgeleitet, das unter anderem eine JTable (jTableFilterTable) enthält.
RecordFilterGUI enthält außerdem eine innere Klasse RecordFilterTableModel für das TableModel diese jTableFilterTable.
Diese innere Klasse enthält ein String-Array mColumnNames, das die Spaltennamen enthält - es wird direkt initialisiert. Die Initialisierung ist der einzige Schreibzugriff auf das Member mColumnNames.

Für die Initialisierung der Komponenten gibt es zwei Methoden (initComponents für die Sachen, die NetBeans per GUI-Editor macht sowie myInitComponents für meine zusätzlichen Dinge).
In myInitComponents setze ich das Model für jTableFilterTable - dazu erzeuge ich direkt im Aufruf ein neues RecordFilterTableModel.
Sollte ja kein Problem sein - dachte ich.

Aber:
es gibt eine NullPointerException in der Methode getColumnCount der Klasse RecordFilterTableModel.
Dort wird nur auf die Länge des String-Arrays zugegriffen, also kann nur dieses String-Array null sein.
Wie kann das sein?
Das Array wird doch mit new String[] { "..." } initialisiert. Und diese Initialisierung müßte doch geschehen, BEVOR getColumnCount aufgerufen werden kann.

Klar, ich kann das Problem umgehen, indem ich den den auskommentierten Code (Abfrage auf null) reinnehme.

Aber ich würde doch gerne wissen, ob das normal ist, daß Methoden aufgerufen werden können, bevor die Initialisierung des Objekts abgeschlossen ist.
Kann mir das jemand erklären?

Die Java-Version ist 1.4.2_05 von Sun (sowohl beim Übersetzen als auch beim Ausführen).

Hier noch der relevante Teil des Codes:

public class RecordFilterGUI extends javax.swing.JPanel
{
    public RecordFilterGUI()
    {
        initComponents();
        myInitComponents();
    }

private void myInitComponents()
    {
        jTableFilterTable.setModel(new RecordFilterTableModel());
    }

public class RecordFilterTableModel extends DefaultTableModel
    {
        private String [] mColumnNames = new String []
            {
                "Column", "Condition", "Value"
            };

public int getColumnCount()
        {
/*
            if (mColumnNames == null)
                return 0;
            else
 */
            return mColumnNames.length;         //<<<<<<<<<<<<<<<<<<<<<<<<<< hier gibt es ne NullPointerException
        }

//weitere Methoden für das TableModel
    }

//weitere Methoden von RecordFilterGUI
}

cu,
Andreas

--
Warum nennt sich Andreas hier MudGuard?
Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
  1. Hallo MudGuard,

    Die Erzeugung der Instanz läuft in diesem Fall wie folgt ab:
    1. Impliziter Konstruktor der Klasse RecordFilterTableModel wird aufgerufen.
    2. Konstruktor der Klasse DefaultTableModel wird aufgerufen, der den Aufruf per this(...) ein paar mal weiterreicht.
    3. Der Konstruktor von Object wird aufgerufen.
    4. Instanzvariablen von Object werden initialisiert und der Konstruktor weiter ausgeführt.
    5. Instanzvariablen von DefaultTableModel werden initialisiert und die Konstruktoren weiter ausgeführt.
    6. Instanzvariablen von RecordFilterTableModel werden initialisiert und der Konstruktor weiter ausgeführt.
    7. Neue Instanz wird zurückgegeben.

    Das Problem ist jetzt, dass einer der Konstruktoren von DefaultTableModel die überschriebene Methode getColumnCount() aufruft zu einem Zeitpunkt, zu dem die Instanzvariablen von RecordFIlterTableModel noch nicht initialisiert wurden.
    Das die Reihenfolge so ist, hat wohl den Grund, dass man einfach noch keinen Code in einer Kindklasse ausführen kann, wenn man die Elternklasse noch nicht initialisiert hat. Das würde noch sehr viel interessantere Effekte geben ;-)

    Aus der Dokumentation von DefaultTableModel geht übrigens hervor, dass es Klüger ist, AbstractTableModel zu erweitern. DefaultTableModel ist dazu nicht gedacht, auch wenn einiges getan wurde, damit es nicht ganz nach hinten los geht.

    Genauer steht der Instanzierungsvorgang in der Java VM Spezification beschrieben.

    Grüße

    Daniel

    1. Hi,

      Das Problem ist jetzt, dass einer der Konstruktoren von DefaultTableModel die überschriebene Methode getColumnCount() aufruft zu einem Zeitpunkt, zu dem die Instanzvariablen von RecordFIlterTableModel noch nicht initialisiert wurden.

      D.h. die Methoden sind bereits überladen, wenn der Konstruktor der Super-Klasse abgearbeitet wird - die Instanz-Variablen aber noch nicht initialisiert.
      Seltsame Vorgehensweise ...

      Das die Reihenfolge so ist, hat wohl den Grund, dass man einfach noch keinen Code in einer Kindklasse ausführen kann, wenn man die Elternklasse noch nicht initialisiert hat.

      Um so unverständlicher, daß die Methoden zu dem Zeitpunkt schon überladen sind ...

      Aus der Dokumentation von DefaultTableModel geht übrigens hervor, dass es Klüger ist, AbstractTableModel zu erweitern.

      Hab ich gemacht - schon gibt es das beschriebene Problem nicht mehr.

      DefaultTableModel ist dazu nicht gedacht, auch wenn einiges getan wurde, damit es nicht ganz nach hinten los geht.

      Dann hätte ein "final" vor der class stehen sollen ;-)

      Genauer steht der Instanzierungsvorgang in der Java VM Spezification beschrieben.

      Danke.

      cu,
      Andreas

      --
      Warum nennt sich Andreas hier MudGuard?
      Fachfragen per E-Mail halte ich für unverschämt und werde entsprechende E-Mails nicht beantworten. Für Fachfragen ist das Forum da.
      1. Hi Andreas,

        D.h. die Methoden sind bereits überladen, wenn der Konstruktor der Super-Klasse abgearbeitet wird - die Instanz-Variablen aber noch nicht initialisiert.
        Seltsame Vorgehensweise ...

        Bei meiner ersten Beobachtung dieses Umstands hatte ich ebenso reagiert ;-)
        Eigentlich ergibt sich dieses Verhalten aber aus dem Umstand, dass Java als OO-Sprache Polymorphismus unterstützt und damit late-binding erzwungen ist (die Realisierung von Interface-Typen z.B. wäre ohne diese Garantie gar nicht möglich).

        Viele Grüße,
        Martin Jung

      2. Hallo MudGuard,

        Seltsame Vorgehensweise ...

        Nicht unbedingt, dass sich das Verhalten einer Klasse wärend der Initialisierung von dem danach unterscheidet, wäre auch recht verwirrend.
        Außerdem bräuchte man dann eine Sonderbehandlung abstrakter Methoden oder man könnte diese wärend der Initialisierung noch gar nicht aufrufen, was auch nicht sinnvoll ist. Man könnte auch weniger Einfluss auf die Initialisierung der Elternklasse nehmen.
        Das andere Verhalten kann man hingegen sowohl in der Kind- als auch in der Elternklasse simulieren.

        Dann hätte ein "final" vor der class stehen sollen ;-)

        Naja, nicht alles, was nicht optimal ist, muss man gleich verbieten. Mag ja auch Fälle geben, in denen das sinnvoll ist. ;-)

        Grüße

        Daniel