简体   繁体   中英

Why is my JComboBox returning null?

Note: This is a shortened version of my actual code, but pretty much the same in terms of structure. I basically cut out the panel connecting to component code and panel connecting to frame code.

On a Display.java , I have the following. Note that I did not add any listener to the targetEnvironmentComboBox . Not sure if that can be a problem:

public class Display extends JFrame {

    private static JButton executeButton;
    private static JComboBox<String> commandOptionsComboBox, targetEnvironmentComboBox;

    //getters
    public static JButton getExecuteButton()    {   return executeButton;               }
    public static JComboBox<String> getCommandOptionsComboBox()    {    return commandOptionsComboBox;      }
    public static JComboBox<String> getTargetEnvironmentComboBox()      {   return targetEnvironmentComboBox;   }

    public Display() {
          super("Display");
          setLayout(new BorderLayout());
          setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);

          commandOptionsComboBox = new JComboBox(commandOptions.toArray());
          commandOptionsComboBox.addActionListener(new CommandListener());

          executeButton = new JButton("Execute");
          executeButton.addActionListener(new CommandListener());

          targetEnvironmentComboBox = new JComboBox(targetEnvironments.toArray());


        //main method that gets executed at the start of program
        public static void main(String[] args) {
        new Display();

        }
  }

On a separate CommandListener.java , I have the following:

 public class CommandListener implements ActionListener {

JButton executeButton = Display.getExecuteButton();
JComboBox<String> commandOptionsComboBox = Display.getCommandOptionsComboBox();
JComboBox<String> targetEnvironmentComboBox = Display.getTargetEnvironmentComboBox();

@Override
public void actionPerformed(ActionEvent event) {

    if(event.getSource() == executeButton) {
        System.out.println("HGello world");
        executeCommand(event);

    }else if (event.getSource() == commandOptionsComboBox) {
        System.out.println("commandline");
        disableUnusedComponents(event);
    }
}

private void disableUnusedComponents(ActionEvent event) {
     **JComboBox<String> targetEnvironmentComboBox = Display.getTargetEnvironmentComboBox();**
     String command = (String) commandOptionsComboBox.getSelectedItem();
    switch(command) {
          case "-duplicate":
            targetEnvironmentComboBox.setEnabled(false);
            targetEnvironmentComboBox.setVisible(false);
            break;
          default: break;
     }

My question is that when I get the targetEnvironmentComboBox in the commandListener class right before the actionPerformed() method, it throws a Null Pointer Exception . If I remove that I get the targetEnvironmentComboBox in the disableUsedComponents() method where the **, it was able to successfully get the combobox .

Also if I execute the executeButton listener, it was able to get references to all the components in the CommandListener class, but if I execute the listener for the getCommandOptionsComboBox , it returns null for executeButton and targetEnvironmentComboBox .

Anyone can explain why this is the case?

Secondly, I know this is probably not the best implementation. Any suggestions on things I can change to follow better practice?

You're getting a null because the targetEnvironmentComboBox doesn't get initialized until after you try to access it in CommandListener . When a new CommandListener is created, it reads the targetEnvironmentComboBox and stores it to a local variable. And look at where you create the CommandListener :

//Creating CommandListeners, which take the reference to targetEnvironmentComboBox (null)
//Although targetEnvironmentComboBox is later set to a usable value, the local copies still
//have the null reference which is assigned here
commandOptionsComboBox.addActionListener(new CommandListener());
executeButton = new JButton("Execute");
executeButton.addActionListener(new CommandListener());

//initializing targetEnvironmentComboBox, which is still null (the first time at least)
targetEnvironmentComboBox = new JComboBox(targetEnvironments.toArray());

If you put Display.getTargetEnvironmentComboBox() inside the method, it doesn't take a copy of the reference until it's needed, at which point it's been properly initialized.

An easy fix, although not the right one, is to initialize it first:

targetEnvironmentComboBox = new JComboBox(targetEnvironments.toArray());

commandOptionsComboBox.addActionListener(new CommandListener());
executeButton = new JButton("Execute");
executeButton.addActionListener(new CommandListener());

A better solution is to use proper encapsulation, and pass in what you need to access the correct objects in a constructor to the CommandListener s. I'd propose modifying your CommandListener class with something like this:

public class CommandListener implements ActionListener {
    JButton executeButton;
    JComboBox<String> commandOptionsComboBox;
    JComboBox<String> targetEnvironmentComboBox;

    public CommandListener(JButton executeButton,
                           JComboBox<String> commandOptionsComboBox,
                           JComboBox<String> targetEnvironmentComboBox){
        this.executeButton = executeButton;
        this.commandOptionsComboBox = commandOptionsComboBox;
        this.targetEnvironmentComboBox = targetEnvironmentComboBox;
    }

    /* The rest of your code */
}

Then passing those field in (making sure you're passing non-null values), and doing away with all the static variables (or refactoring to something more manageable).

Instead of Using static methods to access an instance, use encapsulation and access it through setters and getters .

The only way you can get the INSTANCE of your targetEnvironmentComboBox object is by having the instance of your Display also "pointing" at the same space in memory , try:

public JComboBox getComobo(){
   return [your_object];
}

instead of:

public static JComboBox getTargetEnvironmentComboBox(){
  // your code . . .
}

and pass the Display instance through the CommandListener constructor.

Here's a simplified example of how to set this sort of thing up in a way that both greatly reduces the amount of boilerplate code and is essentially immune to the kind of initialization problem you're encountering. This is also a fairly typical approach in Swing UI code and, in fact, one of the drivers for the introduction of anonymous inner classes.

public class Display extends JPanel {

    private JButton executeButton;
    private JComboBox<String> commandOptionsComboBox;

    public Display() {

        executeButton = new JButton("Exec");
        commandOptionsComboBox = new JComboBox<>();

        executeButton.addActionListener(new ActionListener()
        {
            @Override
            public void actionPerformed(ActionEvent e)
            {
                System.out.println("Action performed");
                commandOptionsComboBox.setEnabled(false);
            }
        });
    }

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM