洗牌 ObservableList 触发不正确的更改通知
Shuffling ObservableList fires incorrect change notification
方法 FXCollections.shuffle()
仅触发 wasRemoved
更改通知。我们可能知道,洗牌不仅仅是删除,而是删除和添加。
在the documentation中我们可以看到:
Shuffles all elements in the observable list. Fires only one change notification on the list.
如果我没记错的话,一个更改可以同时包含 wasAdded
和 wasRemoved
。真遗憾 wasPermutated
没有被默认的 ObservableList
FX api 触发(或者是吗?)。
测试代码:
public class SimpleMain {
public static void main(String[] args) {
ObservableList<Integer> integers = FXCollections.observableArrayList(1, 2, 3, 4);
integers.addListener(initListener());
FXCollections.shuffle(integers);
}
private static ListChangeListener<Integer> initListener() {
return change -> {
while (change.next()) {
if (change.wasPermutated()) {
System.out.println("wasPermutated");
} else if (change.wasRemoved()) {
System.out.println("wasRemoved");
} else if (change.wasAdded()) {
System.out.println("wasAdded");
}
}
};
}
}
问题
您的代码假定 wasAdded()
和 wasRemoved()
是互斥的,无论您是否有意如此。这个假设是错误的。如果一个或多个连续元素被 替换 那么这两种方法都将 return true
.
请记住,Change
对象和“更改”是有区别的。单个 Change
实例可以进行多个更改。当文档说:
Fires only one change notification on the list.
并不是说只有一个 Change
对象会被发送到 ListChangeListener
。它的意思是 Change
对象只会进行一次更改。换句话说,Change#next()
方法只会在第一次调用时 return true
,因此你的 while
循环只会循环一次。
解决方案
您需要重写代码,因为 wasAdded()
和 wasRemoved()
都可以是 true
。例如,这是一个检查所有类型更改的侦听器:
private static ListChangeListener<Integer> initListener() {
return change -> {
while (change.next()) {
if (change.wasPermutated()) {
System.out.println("wasPermutated");
} else if (change.wasUpdated()) {
System.out.println("wasUpdated");
} else if (change.wasReplaced()) {
System.out.println("wasReplaced");
} else if (change.wasRemoved()) {
System.out.println("wasRemoved");
} else { // only other change type is "added"
System.out.println("wasAdded");
}
}
};
}
上面使用wasReplaced()
,与wasAdded() && wasRemoved()
相同。请注意,如果您使用 if-else-if 结构,则必须在 wasRemoved()
或 wasAdded()
之前检查 wasReplaced()
。否则上面的代码将遇到与您的代码相同的问题。
如果您将上面的代码插入到您的代码中并 运行 您将看到以下输出:
wasReplaced
The documentation of ListChangeListener.Change
对如何实现 ListChangeListener
.
给出了更一般的解释(和示例)
请注意,文档中的示例并未专门检查 wasReplaced()
。相反,它处理在最终 else
块中删除的 和 添加的元素(在 wasPermutated()
和 wasUpdated()
return false
).这是可能的,因为如果没有删除或添加元素,getRemoved()
和 getAddedSubList()
将分别 return 空列表。如果您先处理任何已删除的元素,然后再处理任何添加的元素,则通常会产生与专门处理替换元素相同的效果。但是,根据您的用例,专门处理替换可能会有所帮助。
为什么不排列?
他们实施 shuffle
方法的方式是:
public static void shuffle(ObservableList list, Random rnd) {
Object newContent[] = list.toArray();
for (int i = list.size(); i > 1; i--) {
swap(newContent, i - 1, rnd.nextInt(i));
}
list.setAll(newContent);
}
来源:javafx.collections.FXCollections,JavaFX 15.
如你所见,元素被提取到一个数组中,数组被打乱,然后列表中的元素被替换 与数组。这导致单个“替换更改”被触发。
我在 while 循环中向您的 ListChangeListener 添加了一行:
System.out.println("change: "+change);
我得到的输出是:
change: { [1, 2, 3, 4] replaced by [4, 2, 3, 1] at 0 }
wasRemoved
这暗示了你的错误。字符串表示中使用的动词是“替换”。
去掉监听器中的“其他”并添加对 wasReplaced() 的检查,您会发现这正是您得到的结果。
方法 FXCollections.shuffle()
仅触发 wasRemoved
更改通知。我们可能知道,洗牌不仅仅是删除,而是删除和添加。
在the documentation中我们可以看到:
Shuffles all elements in the observable list. Fires only one change notification on the list.
如果我没记错的话,一个更改可以同时包含 wasAdded
和 wasRemoved
。真遗憾 wasPermutated
没有被默认的 ObservableList
FX api 触发(或者是吗?)。
测试代码:
public class SimpleMain {
public static void main(String[] args) {
ObservableList<Integer> integers = FXCollections.observableArrayList(1, 2, 3, 4);
integers.addListener(initListener());
FXCollections.shuffle(integers);
}
private static ListChangeListener<Integer> initListener() {
return change -> {
while (change.next()) {
if (change.wasPermutated()) {
System.out.println("wasPermutated");
} else if (change.wasRemoved()) {
System.out.println("wasRemoved");
} else if (change.wasAdded()) {
System.out.println("wasAdded");
}
}
};
}
}
问题
您的代码假定 wasAdded()
和 wasRemoved()
是互斥的,无论您是否有意如此。这个假设是错误的。如果一个或多个连续元素被 替换 那么这两种方法都将 return true
.
请记住,Change
对象和“更改”是有区别的。单个 Change
实例可以进行多个更改。当文档说:
Fires only one change notification on the list.
并不是说只有一个 Change
对象会被发送到 ListChangeListener
。它的意思是 Change
对象只会进行一次更改。换句话说,Change#next()
方法只会在第一次调用时 return true
,因此你的 while
循环只会循环一次。
解决方案
您需要重写代码,因为 wasAdded()
和 wasRemoved()
都可以是 true
。例如,这是一个检查所有类型更改的侦听器:
private static ListChangeListener<Integer> initListener() {
return change -> {
while (change.next()) {
if (change.wasPermutated()) {
System.out.println("wasPermutated");
} else if (change.wasUpdated()) {
System.out.println("wasUpdated");
} else if (change.wasReplaced()) {
System.out.println("wasReplaced");
} else if (change.wasRemoved()) {
System.out.println("wasRemoved");
} else { // only other change type is "added"
System.out.println("wasAdded");
}
}
};
}
上面使用wasReplaced()
,与wasAdded() && wasRemoved()
相同。请注意,如果您使用 if-else-if 结构,则必须在 wasRemoved()
或 wasAdded()
之前检查 wasReplaced()
。否则上面的代码将遇到与您的代码相同的问题。
如果您将上面的代码插入到您的代码中并 运行 您将看到以下输出:
wasReplaced
The documentation of ListChangeListener.Change
对如何实现 ListChangeListener
.
请注意,文档中的示例并未专门检查 wasReplaced()
。相反,它处理在最终 else
块中删除的 和 添加的元素(在 wasPermutated()
和 wasUpdated()
return false
).这是可能的,因为如果没有删除或添加元素,getRemoved()
和 getAddedSubList()
将分别 return 空列表。如果您先处理任何已删除的元素,然后再处理任何添加的元素,则通常会产生与专门处理替换元素相同的效果。但是,根据您的用例,专门处理替换可能会有所帮助。
为什么不排列?
他们实施 shuffle
方法的方式是:
public static void shuffle(ObservableList list, Random rnd) { Object newContent[] = list.toArray(); for (int i = list.size(); i > 1; i--) { swap(newContent, i - 1, rnd.nextInt(i)); } list.setAll(newContent); }
来源:javafx.collections.FXCollections,JavaFX 15.
如你所见,元素被提取到一个数组中,数组被打乱,然后列表中的元素被替换 与数组。这导致单个“替换更改”被触发。
我在 while 循环中向您的 ListChangeListener 添加了一行:
System.out.println("change: "+change);
我得到的输出是:
change: { [1, 2, 3, 4] replaced by [4, 2, 3, 1] at 0 }
wasRemoved
这暗示了你的错误。字符串表示中使用的动词是“替换”。 去掉监听器中的“其他”并添加对 wasReplaced() 的检查,您会发现这正是您得到的结果。