【问题标题】:Cleaning up bindings and change listeners on nested properties when parent properties change in javafx当 javafx 中的父属性更改时,清理绑定并更改嵌套属性上的侦听器
【发布时间】:2018-07-23 08:37:47
【问题描述】:

我有一个具有SimpleXXXXProperty 属性的模型类。 Javafx GUI 元素使用绑定或更改侦听器进行更新,例如

textField.textProperty().bind(myModel.myModelStatus());

myModel.myModelStatus().addListener((obj,oldv.newv) -> { update here });

当模型类的实例发生变化时,我重新绑定控件并再次添加侦听器。但是,我可以通过内存使用情况看到旧模型仍然存在于内存中。

我必须怎么做才能删除对模型的所有引用以便清理它?

  • 在再次绑定之前取消绑定
  • 移除监听器
  • 两者

当父属性更改时,是否有更自动的方法来更新嵌套属性上的绑定和侦听器?

【问题讨论】:

  • 如何移除被实现为 lambda 表达式的监听器?
  • 如果您只有绑定,那么如果您在其他任何地方都没有持有模型对象的任何引用,那么就不应该有任何内存泄漏。如果您使用了任何侦听器,则需要删除它们(这意味着您不能使用内联 lambda,因为无法删除它们)。
  • 如果一个属性 p1 绑定到另一个属性 p2 并且有对 p1 的引用,那么也有对 p2 的引用。如果您使用侦听器和p1 的侦听器,并且它引用了对象o,只要有对p1 的引用,就会有对o 的引用。只要没有通过其他对象引用它们,解除绑定/绑定到不同的属性并删除侦听器应该会改变这一点并使对象可用于垃圾回收。不过,删除 lambda 表达式而不将其存储在某处应该是不可能的。 (顺便说一句,GC 可能不会立即声明对象。)
  • 您也可以使用WeakInvalidationListenerWeakChangeListener,但两者都不会用于您的显式重新绑定场景。
  • 我认为同样重要的是要注意,GC 总是可以选择延迟内存回收,正如@fabian 提到的那样。

标签: javafx


【解决方案1】:

要撤消模型的绑定(包括侦听器)时要考虑的几点:

  • 当再次绑定相同的属性(例如p1.bind(p3))时,非定向绑定(p1.bind(p2))会自动解除绑定,但明确地这样做(p1.unbind())并没有什么坏处。
  • 双向绑定(p1.bindBidirectional(p2)Bindings.bindBidirectional(p1, p2))必须明确取消绑定(p1.unbindBidirectional(p2)Bindings.unbindBidirectional(p1, p2))。
  • 侦听器必须未注册 (prop.removeListener(l))。

第三部分是棘手的部分,因为侦听器通常被实现为 lambda 表达式或方法引用。不幸的是,lambda 表达式以及方法引用(!)不是常量:

// lambdas are not constant
InvalidationListener l1 = obs -> {};
InvalidationListener l2 = obs -> {};

assert l1 != l2; // they are NOT identical!

嗯,这对于 lambdas 来说可能很明显,但对于方法引用也是如此,这真的很烦人:

// method references are not constant
Runnable runnable1 = this::anyMethod;
Runnable runnable2 = this::anyMethod;

assert runnable1 != runnable2; // they are NOT identical!

这意味着,如果您希望能够取消注册 lambda 表达式或简单方法引用,则不能将其注册为侦听器:

// if you register listeners on a property like that...
label.textProperty().addListener(obs -> System.out.println(obs));
label.textProperty().addListener(this::handleLabelInvalid);

// ...these calls WON'T remove them due to the inequality shown above!
label.textProperty().removeListener(obs -> System.out.println(obs));
label.textProperty().removeListener(this::handleLabelInvalid);

解决方案

您必须自己存储对 lambda 表达式或方法引用的引用。我曾经为此使用 final 字段:

public class MyClass {
    // store references for adding/removal
    private final InvalidationListener l1 = this::handleLabelInvalid;
    private final InvalidationListener l2 = obs -> System.out.println(obs);

    ...

    public void bind() {
        label.textProperty().addListener(l1);
        label.textProperty().addListener(l2);
    }

    public void unbind() {
        label.textProperty().removeListener(l1);
        label.textProperty().removeListener(l2);
    }

    private void handleLabelInvalid(Observable observable) { ... }
}

【讨论】:

  • 如果在同一属性上完成了新的绑定,则无需手动调用unbind()
  • @Jai 如果是bindBidirectional 是否需要?
  • @geometrikal 好点。是的,你确实需要。您需要通过prop.unbindBidirectional(oldProp) 删除(即您需要知道旧属性是什么)。
  • @Jai 你是对的。我修改了答案的第一部分以解决这两个问题 - 单向和双向绑定。
猜你喜欢
  • 1970-01-01
  • 2013-08-16
  • 2014-11-10
  • 2013-01-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-11-07
  • 1970-01-01
相关资源
最近更新 更多