Subclass of abstract JDialog -> NullPointerException

Hello!

I wanted to make an abstract subclass of JDialog that will handle a few things that are common to several of the dialogs in my program. Things like creating and setting up the Ok/Cancel buttons, handling the case where the user closes the dialog with the red X as though they clicked Cancel, etc. The method that is called when the Ok button is clicked is abstract and is supposed to be implemented by the subclasses.

The problem is, when the subclass implements that ok() method and tries to access one of its own instance variables (a JTextField), it throws a NullPointerException. I'm certain this JTextField instance was created, because it was added to the dialog's content pane; I can see it and type text into it. The reference is also visible across method calls after it's created. But when the Ok button is clicked and the method tries to access it, it's always null for some reason that I can't figure out. I tried refactoring the inner classes to their own class files, but it gave me the same results.

Here's a SSCCE. I tried to keep it as short as possible. The comments should highlight the relevant parts of the code. I can do this a few other ways, so I'm not really looking for a JDialog tutorial or anything. I'm mainly just curious why this doesn't work. Can anyone tell me what I'm doing wrong here?

import java.awt.BorderLayout;

import java.awt.EventQueue;

import java.awt.GridLayout;

import java.awt.event.ActionEvent;

import java.awt.event.ActionListener;

import java.awt.event.WindowAdapter;

import java.awt.event.WindowEvent;

import javax.swing.JButton;

import javax.swing.JDialog;

import javax.swing.JFrame;

import javax.swing.JPanel;

import javax.swing.JTextField;

publicclass Main{

// This creates and shows JFrame with a button. Clicking the

// button displays a SomeDialog instance that prompts the user

// to enter a String value.

publicstaticvoid main(String[] args){

final JFrame frame =new JFrame();

final JButton btnShowDialog =new JButton("Show Dialog");

frame.setSize(340,280);

frame.setLocationRelativeTo(null);

frame.getContentPane().add(btnShowDialog, BorderLayout.SOUTH);

frame.addWindowListener(new WindowAdapter(){

publicvoid windowClosing(WindowEvent e){

frame.dispose();

}

});

btnShowDialog.addActionListener(new ActionListener(){

publicvoid actionPerformed(ActionEvent e){

SomeDialog dlg =new SomeDialog(frame);

dlg.setVisible(true);

if (dlg.isCancelled() ==true){

System.out.println("Cancelled");

}else{

String value = dlg.getValue();

System.out.println("Value: "+value);

}

}

});

EventQueue.invokeLater(new Runnable(){

publicvoid run(){

frame.setVisible(true);

}

});

}

// This is intended to provide some basic dialog behavior.

privatestaticabstractclass AbstractDialogextends JDialog{

privateboolean cancelled =false;

private JButton btnOk =new JButton("Ok");

private JButton btnCancel =new JButton("Cancel");

protectedabstractvoid initComponents();

protectedabstractvoid ok();

public AbstractDialog(JFrame parent){

super(parent,true);

addWindowListener(new WindowAdapter(){

publicvoid windowClosing(WindowEvent e){

cancelled =true;

}

});

// This call to initComponents() should cause the subclass to

// create its txtValue instance long before the Ok button is

// clicked.

initComponents();

pack();

setLocationRelativeTo(parent);

}

protected JPanel buildButtons(){

btnOk.addActionListener(new ActionListener(){

publicvoid actionPerformed(ActionEvent e){

cancelled =false;

ok();

}

});

btnCancel.addActionListener(new ActionListener(){

publicvoid actionPerformed(ActionEvent e){

cancelled =true;

dispose();

}

});

JPanel pnlGrid =new JPanel(new GridLayout(1,2,5,5));

pnlGrid.add(btnOk);

pnlGrid.add(btnCancel);

return pnlGrid;

}

publicboolean isCancelled(){

return cancelled;

}

}

// This implements AbstractDialog to prompt the user for a

// String value.

privatestaticclass SomeDialogextends AbstractDialog{

// txtValue is a class-level instance in the subclass.

private JTextField txtValue =null;

private String value =null;

public SomeDialog(JFrame parent){

super(parent);

}

@Override

protectedvoid initComponents(){

// The class-level JTextField instance is created here.

// I would expect this instance to persist throughout the

// life of this dialog.

txtValue =new JTextField();

getContentPane().add(txtValue, BorderLayout.NORTH);

getContentPane().add(buildButtons(), BorderLayout.SOUTH);

// This check is to make sure that the txtValue reference

// is non-null across method calls. It always prints

// "Not null"

checkNull();

}

privatevoid checkNull(){

if (txtValue ==null){

System.out.println("Null");

}else{

System.out.println("Not null");

}

}

@Override

protectedvoid ok(){

// *** The NullPointerException is thrown at this line: ***

value = txtValue.getText();

dispose();

}

public String getValue(){

return value;

}

}

}

[11083 byte] By [BinaryDigita] at [2007-11-27 9:17:32]
# 1

The problem is that the text field is created, but then you set it to null in the derived class. This may seem counter-intuitive at first, but remember that the super-class constructor always runs first, and then additional initialization takes place in the derived class. You have the following:

// txtValue is a class-level instance in the subclass.

private JTextField txtValue = null;

private String value = null;

public SomeDialog(JFrame parent) {

super(parent);

}

First super(parent) is called, and your text field is created through the call to initComponents(). Then the initialization in the SomeDialog class takes place, and there you set txtValue to null.

There are two bad practices contributing to this confusing situation:

1. You are calling an overridden method from a constructor. If used carefully this is not a problem, if used carelessly you end up wasting hours tracking down mysterious bugs (like this one).

2. You are explicitly initializing instance variables to null. This is always completely unnecessary, and in this case it actually causes your program to fail.

In short, to get rid of this problem, do not set txtValue explicitly to null:

// txtValue is a class-level instance in the subclass.

private JTextField txtValue;

private String value;

public SomeDialog(JFrame parent) {

super(parent);

}

Torgila at 2007-7-12 22:08:06 > top of Java-index,Desktop,Core GUI APIs...
# 2
That's amazing. Great catch, I never would have guessed that was the problem. Thank you very much, for both your help and suggestions.
BinaryDigita at 2007-7-12 22:08:06 > top of Java-index,Desktop,Core GUI APIs...