通过其 changeListener 修改 ObservableList 抛出 UnsupportedOperationException
Modifing ObservableList through its changeListener throws UnsupportedOperationException
我正在制作一个简单的 tic-tac-toe GUI-based 游戏。为此,我使用了一个 2D ObservableList,其中填充了 Figures 枚举器。每一步我都会将所选单元格的值更改为 X 或 O,然后我检查是否有人赢了或者游戏是否以平局结束。如果是这样,我用空白重新填充列表。至少它应该是这样工作的。我遇到了标题中描述的问题。这个问题在任何情况下都不会发生,我不明白这里发生了什么,也没有在 Google 上找到它。这是我的代码的简化版本:(感谢帮助)
public class Example{
enum Figures { NONE, X, O }
static ObservableList<ObservableList<Figures>> list = FXCollections.observableArrayList();
public static void main(String... args) {
for (int i = 0; i < 3; i++) {
ObservableList<Figures> subList = FXCollections.observableArrayList();
list.add(subList);
subList.addListener((ListChangeListener<Figures>) change -> {
while (change.next()) {
Figures figure = subList.get(change.getFrom());
if (figure == Figures.NONE) {
//show Blank space
} else {
if (figure == Figures.X) {
//show X
}
if (figure == Figures.O) {
//show O
}
checkState();
}
}
});
for (int j = 0; j < 3; j++) {
subList.add(Figures.NONE);
}
}
//it is a graphical game but here is what I do by clicking buttons to get the exception
list.get(2).set(0, Figures.X);
list.get(1).set(0, Figures.O);
list.get(1).set(1, Figures.X);
list.get(0).set(1, Figures.O);
list.get(0).set(2, Figures.X);
}
private static void checkState() {
//checking state and if win or draw
restartGame();
}
private static void restartGame() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
list.get(i).set(j, Figures.NONE);
}
}
}
}
和输出
Exception in thread "JavaFX Application Thread" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.add(Collections.java:1314)
at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
at javafx.collections.ListChangeBuilder.nextSet(ListChangeBuilder.java:453)
at javafx.collections.ObservableListBase.nextSet(ObservableListBase.java:115)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:162)
at Main.restartGame(Main.java:239)
at Main.lambda$gameScene(Main.java:79)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
at Main.gameScene(Main.java:170)
at Main.start(Main.java:27)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication11(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait4(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null2(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater3(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "JavaFX Application Thread" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.add(Collections.java:1314)
at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
at javafx.collections.ListChangeBuilder.nextSet(ListChangeBuilder.java:453)
at javafx.collections.ObservableListBase.nextSet(ObservableListBase.java:115)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:162)
at Main.restartGame(Main.java:239)
at Main.lambda$gameScene(Main.java:79)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
at Main.gameScene(Main.java:171)
at Main.start(Main.java:27)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication11(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait4(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null2(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater3(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
问题是 ListChangeListener
中的 checkState()
函数。根据 ListChangeListener
处的 javadoc
Warning: This class directly accesses the source list to acquire information about the > changes.
This effectively makes the Change object invalid when another change occurs on the list.
For this reason it is not safe to use this class on a different thread.
It also means the source list cannot be modified inside the listener since that would invalidate this Change object for all subsequent listeners.
我正在制作一个简单的 tic-tac-toe GUI-based 游戏。为此,我使用了一个 2D ObservableList,其中填充了 Figures 枚举器。每一步我都会将所选单元格的值更改为 X 或 O,然后我检查是否有人赢了或者游戏是否以平局结束。如果是这样,我用空白重新填充列表。至少它应该是这样工作的。我遇到了标题中描述的问题。这个问题在任何情况下都不会发生,我不明白这里发生了什么,也没有在 Google 上找到它。这是我的代码的简化版本:(感谢帮助)
public class Example{
enum Figures { NONE, X, O }
static ObservableList<ObservableList<Figures>> list = FXCollections.observableArrayList();
public static void main(String... args) {
for (int i = 0; i < 3; i++) {
ObservableList<Figures> subList = FXCollections.observableArrayList();
list.add(subList);
subList.addListener((ListChangeListener<Figures>) change -> {
while (change.next()) {
Figures figure = subList.get(change.getFrom());
if (figure == Figures.NONE) {
//show Blank space
} else {
if (figure == Figures.X) {
//show X
}
if (figure == Figures.O) {
//show O
}
checkState();
}
}
});
for (int j = 0; j < 3; j++) {
subList.add(Figures.NONE);
}
}
//it is a graphical game but here is what I do by clicking buttons to get the exception
list.get(2).set(0, Figures.X);
list.get(1).set(0, Figures.O);
list.get(1).set(1, Figures.X);
list.get(0).set(1, Figures.O);
list.get(0).set(2, Figures.X);
}
private static void checkState() {
//checking state and if win or draw
restartGame();
}
private static void restartGame() {
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
list.get(i).set(j, Figures.NONE);
}
}
}
}
和输出
Exception in thread "JavaFX Application Thread" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.add(Collections.java:1314)
at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
at javafx.collections.ListChangeBuilder.nextSet(ListChangeBuilder.java:453)
at javafx.collections.ObservableListBase.nextSet(ObservableListBase.java:115)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:162)
at Main.restartGame(Main.java:239)
at Main.lambda$gameScene(Main.java:79)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
at Main.gameScene(Main.java:170)
at Main.start(Main.java:27)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication11(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait4(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null2(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater3(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "JavaFX Application Thread" java.lang.UnsupportedOperationException
at java.util.Collections$UnmodifiableList.add(Collections.java:1314)
at javafx.collections.ListChangeBuilder.nextRemove(ListChangeBuilder.java:208)
at javafx.collections.ListChangeBuilder.nextSet(ListChangeBuilder.java:453)
at javafx.collections.ObservableListBase.nextSet(ObservableListBase.java:115)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:162)
at Main.restartGame(Main.java:239)
at Main.lambda$gameScene(Main.java:79)
at com.sun.javafx.collections.ListListenerHelper$SingleChange.fireValueChangedEvent(ListListenerHelper.java:164)
at com.sun.javafx.collections.ListListenerHelper.fireValueChangedEvent(ListListenerHelper.java:73)
at javafx.collections.ObservableListBase.fireChange(ObservableListBase.java:233)
at javafx.collections.ListChangeBuilder.commit(ListChangeBuilder.java:482)
at javafx.collections.ListChangeBuilder.endChange(ListChangeBuilder.java:541)
at javafx.collections.ObservableListBase.endChange(ObservableListBase.java:205)
at javafx.collections.ModifiableObservableListBase.set(ModifiableObservableListBase.java:163)
at Main.gameScene(Main.java:171)
at Main.start(Main.java:27)
at com.sun.javafx.application.LauncherImpl.lambda$launchApplication11(LauncherImpl.java:863)
at com.sun.javafx.application.PlatformImpl.lambda$runAndWait4(PlatformImpl.java:326)
at com.sun.javafx.application.PlatformImpl.lambda$null2(PlatformImpl.java:295)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.application.PlatformImpl.lambda$runLater3(PlatformImpl.java:294)
at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambda$null7(WinApplication.java:177)
at java.lang.Thread.run(Thread.java:748)
问题是 ListChangeListener
中的 checkState()
函数。根据 ListChangeListener
Warning: This class directly accesses the source list to acquire information about the > changes. This effectively makes the Change object invalid when another change occurs on the list. For this reason it is not safe to use this class on a different thread. It also means the source list cannot be modified inside the listener since that would invalidate this Change object for all subsequent listeners.