Quantumcat
Quantumcat

Reputation: 23

Accessing member variable of enclosing scope in anonymous inner class

EDIT: I have fixed it thanks to rsutormin's link about making the simplest program possible that exhibits the same error. I did this, and it kept working. I added more and more things from the original that I could imagine would contribute and it just kept on working. At first was annoyed but then I realised that if it kept on working, eventually I'd just have a copy of my program, except working. So that was fine. Eventually I found the thing that when added caused the same behaviour as before. In the action event listener method the other cases (besides the button) were for sorting (alphabetical, by size, etc). After the switch statement I had

setTableRows();
update();

and it was only after adding this back (update() fixes the column widths and setTableRows() redoes the content in the new order in case it has been resorted) that the problem reappeared. Specifically setTableRows(). I still don't know what it is exactly that would cause a problem with the new thread there that isn't there when it isn't (I am thinking something to do with the fact that the thread is running WHILE the point of execution continues onto this bit - but the method does not edit thingList, only read it...?), but here is the method in case someone else has a similar problem and can identify something in common with their own code.

private void setTableRows()
{
    DefaultTableModel dtm = new DefaultTableModel(0, 0);
    String[] header = new String[]{"Name", "Size"};
    dtm.setColumnIdentifiers(header);
    for(Thing thing : thingList.getList())
    {
        String sizeColumn = Integer.toString(thing.getSize()) + "MB";
        if(sizeColumn.equals("0MB"))
        {
            sizeColumn = "?";
        }
        dtm.addRow(new Object[]{thing.getTitle(), sizeColumn});
    }
    thingTable.setModel(dtm);
}

Summary: I have a Java Swing program that has a slow, long-running job to do on the click of a button. The UI was freezing so I thought I should do it in a new Thread. However, the member variables of the main window are not getting passed properly to the thread. Only the parts that get created on construction are available, nothing else changed afterwards is available.

Most relevant section:

public class MainWindow extends JPanel implements ActionListener
{

    ......

    @Override
    public void actionPerformed(ActionEvent e)
    {

        switch(e.getActionCommand())
        {
            ...
          case "go":
          Thread t = new Thread()
            {
                @Override
                public void run()
                {
                    try
                    {
                        DoStuff d = new DoStuff(statusLabel);
                        d.doStuff(thingList);
                    }
                    catch (IOException | InterruptedException e1)
                    {
                        e1.printStackTrace();
                    }
                }
            };
            t.start();

            break;
        }
    ....
}

The MainWindow (extends JPanel implements ActionListener) has these member variables:

ThingList thingList

and

JTextArea statusLabel

(and many others that aren't relevant)

ThingList is a class with two relevant member variables:

ArrayList<Thing> list

and

ArrayList<Thing> selectedList

list is filled in the constructor of ThingList, and thingList is created in MainWindow's constructor. selectList is filled when the user clicks on a JTable in its listener, and things are added like this in ThingList:

public void setSelection(int[] rows)
{
    selectedList.clear();
    for(int i = 0; i < rows.length; i ++)
    {
        selectedList.add(list.get(rows[i]));
    }
}

Where the rows passed are the ones clicked on.

Thing is just a data-holding class with getters and setters and that's all.

...

The actual behaviour

I have been trying to read a lot about inner classes and everything seems to say that they should be able to use the enclosing class' member variables without issue, but just that local variables might have to be final. I am guessing it is different because of being a Thread not just a regular run-of-the-mill inner class. But I can't seem to find anyone with this same issue.

Thanks in advance for any help.

Edit: Just had an idea to try and print out the memory addresses of the objects and see if they are the same ones (I had assumed not). I get this result:

thingList address outside of the new thread: main.ThingList@332f9531
selectedList address outside of the new thread: [main.Thing@3f12d523]
thingList address inside run(): main.ThingList@332f9531
selectedList address inside run(): []

From having

System.out.println("thingList address outside of the new thread: " + thingList);
            System.out.println("selectedList address outside of the new thread: " + thingList.getSelectedList());

Just before Thread t = new Thread(), and

System.out.println("thingList address inside run(): " + thingList);
                    System.out.println("selectedList address inside run(): " + thingList.getSelectedList());

just inside run() (before try{ )

So they are the same object (I am assuming that's what the hash code represents - that it has the same memory location?) but selectedList is blank (or whatever [] signifies) by the time it is inside the thread.

Upvotes: 1

Views: 509

Answers (1)

rsutormin
rsutormin

Reputation: 1649

Maybe you need "SwingUtilities.invokeLater()" to be able to correctly change properties of Swing-related components? Here is my way of doing it:

import java.awt.Color;

import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.SwingUtilities;

public class ColorFrame extends JFrame {
    private JPanel innerPanel = new JPanel();
    
    public static void main(String[] args) {
        new ColorFrame().setVisible(true);
    }
    
    public ColorFrame() {
        this.add(innerPanel);
        this.setSize(500, 500);
        this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        new Thread() {
            @Override
            public void run() {
                while(true) {
                    try {
                        Thread.sleep(1000);
                    } catch(Exception ex) {
                        break;
                    }
                    SwingUtilities.invokeLater(new Runnable() {
                        @Override
                        public void run() {
                            int r = (int)(255 * Math.random());
                            int g = (int)(255 * Math.random());
                            int b = (int)(255 * Math.random());
                            ColorFrame.this.innerPanel.setBackground(new Color(r, g, b));
                        }
                    });
                }
            }
        }.start();
    }
}

Upvotes: 1

Related Questions