我正在 javafx 中编写一个类,其中有两个双向绑定的属性,并且正在测试一些边缘情况。我发现,如果您从失效侦听器内部更改其中一个属性的值,则会导致属性不同步。这是一个小例子:
static class A {
IntegerProperty x = new SimpleIntegerProperty(this, "x", 0);
IntegerProperty y = new SimpleIntegerProperty(this, "y", 0);
A() {
x.bindBidirectional(y);
}
}
static void testA() {
A a = new A();
a.x.addListener( o -> { //InvalidationListener
if (a.x.get() < 0) a.x.set(0);
});
// after y is set to a negative value, x and y hold different values
// until either y is set to a value that's >= 0 or x is set to any value
a.y.set(-2);
System.out.println(a.x.get());
System.out.println(a.y.get());
}
输出:
0
-2
我假设在使用双向绑定时,更改一个属性总是会导致另一个属性被更新。人们似乎很少(而且可能是不明智的)编写这样的失效侦听器,但我在这里防御性地思考。如果这些属性至少有一个被暴露,我不希望它能够破坏我的类的任何不变量。我想这里有三种可能的解释:
双向绑定的契约并不意味着它们总是同步的(要么它们保持相同的值,要么它们被标记为无效),它只是在尽力而为的基础上。因此,类不变量不应该基于这个事实。
更改失效侦听器内的值会破坏双向绑定的前提条件,应避免。否则它们总是同步的。因此,您可以基于这个事实创建一个类不变量,因为要求客户端不应该编写这样的失效侦听器是合理的。
这是一个错误,无论如何,它们确实是同步的。我想如果这是真的,那么您可以轻松地生成更改通知的无限循环(例如向 y 添加一个始终将值设置为 < 0). So, it would be up to the client to prevent such cases.
的失效侦听器)这些解释是否接近事实,还是我在这里遗漏了其他内容?另外,我很想知道是否存在其他类型的绑定操作考虑到这些情况。
我认为你的第二个理论是正确的:
更改失效侦听器内的值会破坏双向绑定的前提条件,应避免。否则它们总是同步的。因此,您可以基于这个事实创建一个类不变量,因为要求客户端不应该编写这样的失效侦听器是合理的。
要了解双向绑定操作,您可以查看源代码。
例如参见TypedNumberBidirectionBinding。它有一个标志
updating
,用于设置并检查属性何时无效。如果在失效侦听器内再次更新属性,则更新标志将为 true,并且将跳过属性值的同步。
(出于示例目的,简化了失效代码)。
if (!updating) {
updating = true;
try {
if (property1 == sourceProperty) {
T newValue = property1.getValue();
property2.setValue(newValue);
property2.getValue();
oldValue = newValue;
} else {
T newValue = (T)property2.getValue();
property1.setValue(newValue);
property1.getValue();
oldValue = newValue;
}
} finally {
updating = false;
}
}
InvalidationListener javadoc 指出:
一般来说,在此方法中修改观测值被认为是不好的做法。
因此,我建议您不要“从失效侦听器内部更改其中一个属性的值”。
滑块类中 valueProperty 的双向绑定在超出范围时将无法正确同步
这是因为 Slider 类做了不推荐的事情。它修改失效监听器中滑块值属性的值。
修改是将变化值钳制在滑块范围内。
这会导致 Slider 类中 valueProperty 的双向绑定在越界时无法正确同步。
该值必须始终介于最小值和最大值之间(含边界值)。如果由于最小或最大更改或由于自身更改而超出范围,则它将被限制以始终保持有效。