1

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

  • In doStuff, the passed-in ThingList has a correctly populated list but an empty selectedList

  • If you breakpoint inside run(), and hover over one of the variables, they appear bold like if you hovered over a variable that was out of scope at the point of execution (this is in Eclipse).

  • If you make a local variable just before the Thread definition and use that instead, it doesn't appear bolded when hovered over (and it has an id and you can go into the two lists and look at their data), but list is still populated while selectedList is empty

  • If you give Thread a name and its own member variables, and pass the real ones in in a constructor, it behaves as above.

  • If you add a dummy Thing to selectedList inside the method that populates list, that appears in the passed-in ThingList to doStuff. But I can't know in advance what the user will click on so I can't populate selectedList in the constructor as a solution.

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.

Quantumcat
  • 23
  • 4
  • See http://stackoverflow.com/questions/5107158/how-to-pass-parameters-to-anonymous-class. You can an additional method in your anonymous class to receive data from containing class. – KC Wong Nov 02 '16 at 02:43
  • Thanks KC, I hadn't seen that thread before. I tried the answer that was adding .init(variables) at the end and having instance variables in the class but it didn't work :-( Same result as when I tried giving it a name and having a constructor to give it its own instance variables, and when I made local variables and used those instead of the class variables. Even in the thread you linked, it says that inner classes can access the instance variables of the enclosing class ... but I can't (or can only partially)? It's really weird. – Quantumcat Nov 02 '16 at 04:39
  • Hang on. What is `thingList.getSelectedList()`? And who is changing it? And where does it show as being identical with `selectedList()`? – user207421 Nov 02 '16 at 05:01
  • @EJP just practicing encapsulation :-) This is getSelectedList() (in ThingList): public ArrayList getSelectedList() { return selectedList; } – Quantumcat Nov 02 '16 at 05:31
  • @Quantumcat Refer to the Java Language Specification, 8.1.3: https://docs.oracle.com/javase/specs/jls/se7/html/jls-8.html#jls-8.1.3 Particularly this line: "Any local variable, formal parameter, or exception parameter used but not declared in an inner class must be declared final. " and the example 8.1.3-2. – KC Wong Nov 02 '16 at 05:44
  • I understand about local variables - but what are formal and exception parameters? In the article it says: "Inner classes whose declarations do not occur in a static context may freely refer to the instance variables of their enclosing class." this class doesn't occur in a static context (it's in the method public void actionPerformed(ActionEvent e) - no static). So shouldn't I be able to freely use the instance variables? – Quantumcat Nov 02 '16 at 06:23

1 Answers1

1

Maybe you need "SwingUtilities.invokeLater()" to be able to correctly change properties of Swing-related components? Anyway it's much better to prepare Minimal, Complete and Verifiable Example (https://stackoverflow.com/help/mcve). 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();
    }
}

Can you please check that my example covers your problem?

Community
  • 1
  • 1
rsutormin
  • 1,629
  • 2
  • 17
  • 21
  • This is going to be the answer!!!! I copied your anonymous thread over mine, changed ColorFrame to MainWindow and innerPanel to statusLabel, and clicking the button made the panel start changnig colours! Thank you for the link, I will read it and try and make my next question a lot better. – Quantumcat Nov 02 '16 at 05:39
  • No, didn't work, with or without the while(true) loop :-( (replacing the contents of run() from the invokeLater to be DoStuff d = new DoStuff(statusLabel) etc) Does it mean anything that before the new thread, the modCount of selectedList is 5 (with a size of 1 if I select one thing), but inside the thread (once it is empty) it has a modCount of 9? – Quantumcat Nov 02 '16 at 06:00
  • @Quantumcat, I don't get what is happening in your case. It's very hard to guess without working example (MCVE). I think you should spend 10 minutes and update your question with minimal working example. In your current state we can only guess how to reproduce it on our machines. For simplicity you can add print statements instead of real UI actions. – rsutormin Nov 02 '16 at 15:40
  • Thank you rsutormin. Making an MCVE helped me find what was causing the problem, even though I have no idea HOW it did that! In future when I have a problem, if I can't figure it out through normal means, I will make an MCVE. Either I have a great little example to ask for help with, OR I'll end up solving it, OR I'll end up with a copy of my program but working this time! – Quantumcat Nov 03 '16 at 21:34
  • @Quantumcat, exactly. :) I'm glad it helped. – rsutormin Nov 04 '16 at 01:35