访问匿名内部封闭作用域的成员变量 class
Accessing member variable of enclosing scope in anonymous inner class
编辑:由于 rsutormin 的 link 使出现相同错误的最简单程序成为可能,我已经修复了它。我这样做了,它一直在工作。我从原版中添加了越来越多我认为会有所贡献的东西,它一直在工作。起初很生气,但后来我意识到,如果它继续工作,最终我只会得到我的程序的副本,除了工作。所以那很好。
最终我发现添加时会导致与以前相同的行为。在动作事件侦听器方法中,其他情况(除了按钮)用于排序(按字母顺序、按大小等)。在 switch 语句之后我有
setTableRows();
update();
并且只有在重新添加它之后(update() 修复了列宽并且 setTableRows() 以新的顺序重做内容以防万一它已被使用)问题再次出现。特别是 setTableRows()。我仍然不知道究竟是什么会导致那里的新线程出现问题,而当它不存在时却不存在(我在想与线程 运行ning 这一事实有关虽然执行点继续到这一点 - 但该方法不编辑 thingList,只读取它......?),但这是万一其他人有类似问题并且可以识别与他们自己的代码有共同之处的方法.
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);
}
总结:
我有一个 Java Swing 程序,它有一个缓慢的、长时间的 运行ning 工作,只需单击一个按钮即可完成。 UI 太冷了,所以我想我应该在一个新线程中完成它。但是,main window 的成员变量没有正确传递给线程。只有构建时创建的部分可用,之后没有其他更改可用。
最相关的部分:
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;
}
....
}
MainWindow(扩展 JPanel 实现 ActionListener)有这些成员变量:
ThingList thingList
和
JTextArea statusLabel
(以及许多其他不相关的)
ThingList 是一个 class,有两个相关的成员变量:
ArrayList<Thing> list
和
ArrayList<Thing> selectedList
list是在ThingList的构造函数中填写的,而thingList是在MainWindow的构造函数中创建的。当用户在它的监听器中点击一个 JTable 时,selectList 被填充,并且在 ThingList 中添加了这样的东西:
public void setSelection(int[] rows)
{
selectedList.clear();
for(int i = 0; i < rows.length; i ++)
{
selectedList.add(list.get(rows[i]));
}
}
传递的行是单击的行。
Thing 只是一个带有 getter 和 setter 的数据保存 class,仅此而已。
...
实际行为
在 doStuff 中,传入的 ThingList 有一个正确填充的列表,但有一个空的 selectedList
如果您在 运行() 内设置断点,并将鼠标悬停在其中一个变量上,它们将显示为粗体,就像您将鼠标悬停在执行时超出范围的变量上一样(这是在 Eclipse 中)。
如果你在 Thread 定义之前创建一个局部变量并使用它,当鼠标悬停在上面时它不会显示为粗体(并且它有一个 id,你可以进入两个列表和查看他们的数据),但是当 selectedList 为空时列表仍然存在
如果你给线程一个名字和它自己的成员变量,并在构造函数中传递真实的,它的行为如上。
如果您在填充列表的方法中向 selectedList 添加一个虚拟事物,它会出现在传递给 doStuff 的 ThingList 中。但是我无法提前知道用户会点击什么,所以我无法在构造函数中填充 selectedList 作为解决方案。
我一直在尝试阅读很多关于内部 classes 的内容,所有内容似乎都在说他们应该能够毫无问题地使用封闭的 class' 成员变量,但只是局部变量变量可能必须是最终的。我猜它是不同的,因为它是一个线程,而不仅仅是一个普通的 运行-of-the-mill 内部 class。但我似乎找不到有同样问题的人。
在此先感谢您的帮助。
编辑:
只是有一个想法尝试打印出对象的内存地址,看看它们是否相同(我假设不是)。我得到这个结果:
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(): []
来自
System.out.println("thingList address outside of the new thread: " + thingList);
System.out.println("selectedList address outside of the new thread: " + thingList.getSelectedList());
就在 Thread t = new Thread() 之前,并且
System.out.println("thingList address inside run(): " + thingList);
System.out.println("selectedList address inside run(): " + thingList.getSelectedList());
就在 运行() 里面(在 try{ 之前)
所以它们是同一个对象(我假设这就是哈希码所代表的 - 它具有相同的内存位置?)但是当它进入线程时 selectedList 是空白的(或者 [] 表示的任何内容) .
也许您需要 "SwingUtilities.invokeLater()" 才能正确更改与 Swing 相关的组件的属性?无论如何,最好准备最小的、完整的和可验证的示例 (https://whosebug.com/help/mcve)。这是我的做法:
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();
}
}
你能检查一下我的例子是否涵盖了你的问题吗?
编辑:由于 rsutormin 的 link 使出现相同错误的最简单程序成为可能,我已经修复了它。我这样做了,它一直在工作。我从原版中添加了越来越多我认为会有所贡献的东西,它一直在工作。起初很生气,但后来我意识到,如果它继续工作,最终我只会得到我的程序的副本,除了工作。所以那很好。 最终我发现添加时会导致与以前相同的行为。在动作事件侦听器方法中,其他情况(除了按钮)用于排序(按字母顺序、按大小等)。在 switch 语句之后我有
setTableRows();
update();
并且只有在重新添加它之后(update() 修复了列宽并且 setTableRows() 以新的顺序重做内容以防万一它已被使用)问题再次出现。特别是 setTableRows()。我仍然不知道究竟是什么会导致那里的新线程出现问题,而当它不存在时却不存在(我在想与线程 运行ning 这一事实有关虽然执行点继续到这一点 - 但该方法不编辑 thingList,只读取它......?),但这是万一其他人有类似问题并且可以识别与他们自己的代码有共同之处的方法.
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);
}
总结: 我有一个 Java Swing 程序,它有一个缓慢的、长时间的 运行ning 工作,只需单击一个按钮即可完成。 UI 太冷了,所以我想我应该在一个新线程中完成它。但是,main window 的成员变量没有正确传递给线程。只有构建时创建的部分可用,之后没有其他更改可用。
最相关的部分:
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;
}
....
}
MainWindow(扩展 JPanel 实现 ActionListener)有这些成员变量:
ThingList thingList
和
JTextArea statusLabel
(以及许多其他不相关的)
ThingList 是一个 class,有两个相关的成员变量:
ArrayList<Thing> list
和
ArrayList<Thing> selectedList
list是在ThingList的构造函数中填写的,而thingList是在MainWindow的构造函数中创建的。当用户在它的监听器中点击一个 JTable 时,selectList 被填充,并且在 ThingList 中添加了这样的东西:
public void setSelection(int[] rows)
{
selectedList.clear();
for(int i = 0; i < rows.length; i ++)
{
selectedList.add(list.get(rows[i]));
}
}
传递的行是单击的行。
Thing 只是一个带有 getter 和 setter 的数据保存 class,仅此而已。
...
实际行为
在 doStuff 中,传入的 ThingList 有一个正确填充的列表,但有一个空的 selectedList
如果您在 运行() 内设置断点,并将鼠标悬停在其中一个变量上,它们将显示为粗体,就像您将鼠标悬停在执行时超出范围的变量上一样(这是在 Eclipse 中)。
如果你在 Thread 定义之前创建一个局部变量并使用它,当鼠标悬停在上面时它不会显示为粗体(并且它有一个 id,你可以进入两个列表和查看他们的数据),但是当 selectedList 为空时列表仍然存在
如果你给线程一个名字和它自己的成员变量,并在构造函数中传递真实的,它的行为如上。
如果您在填充列表的方法中向 selectedList 添加一个虚拟事物,它会出现在传递给 doStuff 的 ThingList 中。但是我无法提前知道用户会点击什么,所以我无法在构造函数中填充 selectedList 作为解决方案。
我一直在尝试阅读很多关于内部 classes 的内容,所有内容似乎都在说他们应该能够毫无问题地使用封闭的 class' 成员变量,但只是局部变量变量可能必须是最终的。我猜它是不同的,因为它是一个线程,而不仅仅是一个普通的 运行-of-the-mill 内部 class。但我似乎找不到有同样问题的人。
在此先感谢您的帮助。
编辑: 只是有一个想法尝试打印出对象的内存地址,看看它们是否相同(我假设不是)。我得到这个结果:
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(): []
来自
System.out.println("thingList address outside of the new thread: " + thingList);
System.out.println("selectedList address outside of the new thread: " + thingList.getSelectedList());
就在 Thread t = new Thread() 之前,并且
System.out.println("thingList address inside run(): " + thingList);
System.out.println("selectedList address inside run(): " + thingList.getSelectedList());
就在 运行() 里面(在 try{ 之前)
所以它们是同一个对象(我假设这就是哈希码所代表的 - 它具有相同的内存位置?)但是当它进入线程时 selectedList 是空白的(或者 [] 表示的任何内容) .
也许您需要 "SwingUtilities.invokeLater()" 才能正确更改与 Swing 相关的组件的属性?无论如何,最好准备最小的、完整的和可验证的示例 (https://whosebug.com/help/mcve)。这是我的做法:
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();
}
}
你能检查一下我的例子是否涵盖了你的问题吗?