JavaFX Property

使用 JavaFX 的好处之一是它对 Property 和 Binding 的强大而全面的支持。除此之外,Property 还允许你连接场景,这样当你修改它后面的数据时,视图就会自动更新。

属性是可写或只读的可观察对象。在 JavaFX 中有 30 种类型的 Property 对象,包括 StringProperty、SimpleListProperty 和 ReadOnlyObjectProperty。每个属性都包装了一个现有的 Java 对象,添加了监听和绑定的功能。

20220110230824

SimpleDoubleProperty 为例,它集成了 JavaFX 中 binding、property 和 value 的各种重要的 package,每个 package 都增加了JavaFX 属性所展示的最终功能的一个方面。

除了属性功能之外,JavaFX 还通过 Binding 和 Expression 对象提供绑定值的功能。

Binding 是一种强制对象之间关系的机制,其中一个或多个可观察对象被用来更新另一个对象的值。Binding 可以是一个方向,也可以是两个方向,可以直接从属性(Fluent API)或使用 Bindings 实用程序类(Bindings API)创建。

如果需要额外的自定义或性能,也可以手动创建自定义绑定对象。这被称为低级 API。

主要内容

Property 和 Binding 是一组接口和类,旨在大大简化开发人员的工作。也就是说,在 Bindings 类中有 61个 属性和 249 个方法,因此很难管理。

本文将介绍:

  • 什么是属性,哪些属性是可用的
  • 如何观察一个属性值
  • 什么是约束?
  • 如何绑定属性
  • 中间绑定技术(Fluent API 和 Bindings API)
  • 高阶绑定技术(低级 API)

JavaFX 早期的许多问题,比如当你改变某些东西时,场景不能自动更新,这是因为场景与属性的错误连接。JavaFX 场景旨在基于属性和事件进行更新。

如果您想加深对 JavaFX 如何工作的理解,也可以查看关于事件的全面指南。这两篇文章结合起来将为 JavaFX 的后台工作提供一个真正坚实的基础。

什么是 Property

如果你像我一样,并且没有计算机科学背景,那么属性一开始似乎很吓人。不过,引擎盖下面并没有什么魔法。大多数 JavaFX Property 对象扩展了两个关键接口:ReadOnlyProperty<T>WriteableValue<T>

20220110231507

Some of them don’t, though. JavaFX has 10 read-only properties, which extend ReadOnlyProperty<T>, but don’t extend WriteableValue<T>.

JavaFX 有 10 个只读属性,扩展了 ReadOnlyProperty<T>,但没有扩展 WriteableValue<T>

创建 Property

JavaFX 提供了十个内置类,使创建属性变得非常容易。它们实现了所有必需的功能,从监听到绑定。

  • SimpleBooleanProperty
  • SimpleDoubleProperty
  • SimpleFloatProperty
  • SimpleIntegerProperty
  • SimpleListProperty
  • SimpleLongProperty
  • SimpleMapProperty
  • SimpleObjectProperty
  • SimpleSetProperty
  • SimpleStringProperty

您可以定义任何简单的属性对象,可以带有或不带初始值。如果没有定义默认值,它们将默认为属性包装对象的默认值——0、false、 空字符串("")或一个空集合。

SimpleIntegerProperty()
SimpleIntegerProperty(int initialValue)

它们也可以用一个名称和一个 JavaFX 称为属性“bean”的对象来创建。这并没有以任何方式封装属性,而是创建了一个符号链接到表示属性“所有者”的对象。

SimpleIntegerProperty(Object bean, String name)
SimpleIntegerProperty(Object bean, String name, int initialValue)

参数 name 和 bean 都不会改变属性的行为,但它可以作为有用的查找方式。如果您将相同的侦听器附加到多个属性(特别是通过编程生成的属性),那么这很有用。然后,一旦发生更改,您可以使用 bean 和 name 参数来检查刚刚更改的属性。

所有的 JavaFX 属性都有方法来实现以下功能:

  • 侦听对属性值的更改
  • 将属性捆绑在一起(绑定),以便它们自动更新
  • 获取和设置(如果可写)属性值

如何观察属性

正如我们刚才从上面看到的,JavaFX Property 对象是不同实现接口的大杂烩。这在这里很重要,因为这意味着它们提供了两种侦听更改的方式:失效和更改。

20220110233407

失效侦听器:JavaFX 中的每个属性都扩展了 Observable 接口,这意味着它们都提供了注册侦听器的功能,当属性失效时,这些侦听器就会被触发。如果你不熟悉“invalidates”,它是一种将属性标记为潜在更改而不强制其重新计算属性值的方法。

对于具有复杂或昂贵计算的属性,这可能是一个有用的工具,但我发现它们使用得不如更改侦听器多。

更改监听器:在此之上,JavaFX 属性扩展了 ObservableValue<T>,这意味着您可以注册监听器,该监听器只在对象实际更改时触发。与无效侦听器相比,我更经常使用这些侦听器。

更改侦听器允许我们听到更改,并提前提供可执行代码,这些代码将基于 Property 的新旧值执行。

监听更改

您可以通过调用 addListener() 方法在属性上注册一个监听器,提供一个 InvalidationListener(不太常见)或 ChangeListener(更常见)。

要添加更改侦听器,我们可以通过实现 ChangeListener 接口来完整地定义它——这是一个具有一个方法:changed() 的函数接口。

DoubleProperty altitude = new SimpleDoubleProperty(35000);
ChangeListener<Number> changeListener = new ChangeListener<Number>() {
        @Override
        public void changed(ObservableValue<? extends Number> observable, Number oldValue, Number newValue) {
            if (newValue.doubleValue() < 15000) {
                deployParachute();
            }
        }
    };
altitude.addListener(changeListener);

所有的数值属性(double、float、int 和 long)都需要使用 Number 类型参数化的更改侦听器。当然,因为它们是一个功能性接口,我们也可以使用自 Java 8 以来的 lambdas 内联创建它们。

altitude.addListener((observable, oldValue, newValue) -> {
        if (newValue.doubleValue() < 15000) {
            deployParachute();
        }
});

每当属性的值更改时,更改侦听器将触发一次。

什么是 Binding

Binding 是一种将对象连接在一起的方法,强制执行一个对象至少依赖另一个对象的关系。属性本身可以本机绑定,也可以通过创建 Expression 和 Binding 对象来绑定。

Expression 和 Binding 都是可观察对象,它们也依赖于至少一个其他可观察对象的值(但可能更多)。这使您能够创建具有多个计算的表达式链:一种将字符串或数字转换组合在一起的超简单方法。

Expression 和 Binding 在本文的中级和高级部分中。现在,我们只将两个属性绑定在一起,而不需要任何额外的类。

如何绑定属性

在幕后,绑定是侦听更改的特定用例。所有 JavaFX 的绑定 api 都有样板代码,用来监听(至少)一个属性的更改,并使用任何更改来更新该绑定的值。

更改侦听器让我们提前提供可执行代码,而绑定让我们方便地将两个属性串在一起,而不必担心实现更新特定值的问题。

最简单和最常用的方法是附加到 Property 对象本身的方法:bind()bindBidirectional()。它们代表了单向和双向绑定的最简单的选项。

单向绑定

当您在目标属性上调用 bind() 方法时,您将第二个属性作为参数传递给它——绑定源。

StringProperty sourceProperty = new SimpleStringProperty("First Value");
StringProperty targetProperty = new SimpleStringProperty("Second Value");
targetProperty.bind(sourceProperty);

目标存储对新源属性的引用,并侦听更改。当源值更改时,当检测到更改时,它会自动更新目标(本身)。

20220111000825

在本例中,targetProperty 将跟踪 sourceProperty 的值。关于此方法的额外位的几点注意事项:

  • 如果属性当前已经被绑定,则当前绑定将被删除,新绑定将替换它。

  • 如果提供了 null 参数,则该方法抛出一个 NullPointerExeption。

  • 该方法立即复制它正在侦听的属性的值,因此目标属性的当前值将丢失。

双向绑定

当然,可以将两个方向的属性连接在一起,将它们的值连接在一起,这样它们的值总是相同的。为此,我们调用bindBidirectional(),再次将源属性作为参数传递。

StringProperty sourceProperty = new SimpleStringProperty("First Value");
StringProperty targetProperty = new SimpleStringProperty("Second Value");
targetProperty.bindBidirectional(sourceProperty);

如果属性的值不同,那么方法调用的顺序对于确定绑定的起始值很重要。

应用于 targetProperty 的方法在对 sourceProperty 进行交互绑定之前立即更新 targetProperty 的值。这意味着在双向绑定之后,两个属性都将把属性的值作为参数传递(源属性)。

20220111001044

除了被限制为碳复制方法的基本绑定之外,JavaFX 还支持更复杂的绑定:创建一个属性的多个或复杂操作来更新另一个属性。

如果将一个属性绑定到另一个属性不能涵盖您的用例,请不要担心——我将在下面几节中介绍更复杂的绑定。

中间绑定技术

有三种方法可以操作任何属性,并将操作后的值用于绑定:

  • Fluent API – 类似 myProperty.bind(otherProperty).multiply(2) 的方法
  • Bindings API – 类似 Bindings.add(myProperty, otherProperty) 的方法
  • Low-Level API – 创建 DoubleBinding 这样的自定义 Binding 对象

其中两个提供了 cookie-cutter 方法,用于在预定义的实现中绑定属性。我总是发现这些包含了属性绑定的大部分用例,因为它们给了您巨大的灵活性。

低级 API(创建 Binding 对象)可以有更高的性能,但可能会变得复杂得多。在此基础上,我将它分成单独的部分,我将在本文的末尾详细介绍。

Fluent API

Fluent API 依赖于“表达式”对象的创建,该对象类似于属性(它们是可观察值),具有额外的方便方法来支持额外的操作。

属性也可以绑定到表达式,这意味着任何操作的输出都可以用于更新属性,如上所述。这种功能——被观察到并依赖于对象的值——创造了链接的可能性。

20220111001321

在字符串的情况下,我们可以使用它来创建字符串链,它们连接在一起。一旦 sourceProperty 更新,targetProperty 将通过表达式自动更新。

StringProperty sourceProperty = new SimpleStringProperty("It doesn't matter how slowly you go");
StringExpression expression = sourceProperty.concat(" as long as you don't stop");
StringProperty targetProperty = new SimpleStringProperty("");
targetProperty.bind(expression);
System.out.println(targetProperty.get());
//Output: It doesn't matter how slowly you go as long as you don't stop

您可以内联完成所有这些工作,使复杂的代码相对简洁。在本例中,我们将在调用 bind 方法的同时创建 StringExpression。

targetProperty.bind(sourceProperty.concat(" as long as you don't stop"));
System.out.println(targetProperty.get());
//Output: It doesn't matter how slowly you go as long as you don't stop

这可能有点令人困惑,但不要忘记 targetProperty 实际上是绑定到由 concat() 方法创建的 StringExpression 的。绑定到 sourceProperty 的是匿名表达式。

这带来了惊人的好处。API 很丰富,这种风格生成的可读代码涵盖了您需要的大多数操作。

我们也可以使用 Fluent API 来处理数字。

在处理数字时,我们可以链式操作来创建简单、可读的代码,表示我们试图复制的公式。要把角度转换成弧度,你要乘以 π 再除以 180。代码具有很高的可读性。

DoubleProperty degrees = new SimpleDoubleProperty(180);
DoubleProperty radians = new SimpleDoubleProperty();
radians.bind(
  degrees.multiply(Math.PI).divide(180)
);

但是,就性能而言,每个表达式都是链中的一个链接,在初始属性的每次更改时都需要更新。在这个例子中,我们将角度转换为弧度,我们创建了两个可观察值来更新弧度属性。

对于复杂的转换,或者在需要进行大量绑定的情况下,可以考虑使用 Bindings API(如果它提供了所需的灵活性),或者低级 API。

Bindings API

JavaFX 中的 Bindings 类是一个实用程序类,包含 249 个用于属性绑定的方法。它允许你在各种类型的可观察对象之间创建绑定。根据绑定的不同,可以将属性与值结合使用,比如字符串和数字。

20220111001625

有 10 种通用的绑定策略,我将其分为两个主要领域,我将其称为“值操作”和“集合操作”。有些桶装不下,所以我们有了一个不雅的桶,叫做“other”。

Values

  • Mathematics (+, – ÷, ×)
  • Selecting the maximum or minimum
  • Value comparison (=, !=, <, >, <=, >=)
  • String formatting

Collections

  • Binding two collections (lists, maps, sets)
  • Binding values to objects at a certain position in a collection
  • Binding to collection size
  • Whether a collection is empty

Other bindings

  • Multiple-object bindings
  • Boolean operators (and, not or) – (and when!)
  • Selecting values

由于方法的数量庞大,我将深入探讨如何使用我认为(1)经常使用的方法或(2)难以理解的方法。本质上,我会试着用有用的解释增加价值,同时尽量不让你看到 RSI 滚动。

操作值

Bindings API 支持四种简单的数学操作:加法、减法、乘法和除法。它提供了单独的方法来使用浮点型、双精度型、整型和长值,以及两个 ObservableNumberValue 对象(例如 DoubleProperty)之间的方法。

Mathematics

DoubleBinding    add(double op1, ObservableNumberValue op2)
NumberBinding    add(float op1, ObservableNumberValue op2)
NumberBinding    add(int op1, ObservableNumberValue op2)
NumberBinding    add(long op1, ObservableNumberValue op2)
DoubleBinding    add(ObservableNumberValue op1, double op2)
NumberBinding    add(ObservableNumberValue op1, float op2)
NumberBinding    add(ObservableNumberValue op1, int op2)
NumberBinding    add(ObservableNumberValue op1, long op2)
NumberBinding    add(ObservableNumberValue op1, ObservableNumberValue op2)

对于每个数值选项都有相同的方法。为了方便,API 交换了第一个和第二个参数。对于加法和乘法来说,顺序无关紧要,但是对于减法来说,顺序决定了哪个参数要从另一个参数中减去。

DoubleBinding    subtract(double op1, ObservableNumberValue op2)
DoubleBinding    subtract(ObservableNumberValue op1, double op2)
NumberBinding    subtract(ObservableNumberValue op1, ObservableNumberValue op2)

在每种情况下,从第一个参数中减去第二个参数。

DoubleBinding    multiply(double op1, ObservableNumberValue op2)
DoubleBinding    multiply(ObservableNumberValue op1, double op2)
NumberBinding    multiply(ObservableNumberValue op1, ObservableNumberValue op2)

最后,有了划分,秩序又变得重要起来。绑定的值计算为第一个参数除以第二个参数。

DoubleBinding    divide(double op1, ObservableNumberValue op2)
DoubleBinding    divide(ObservableNumberValue op1, double op2)
NumberBinding    divide(ObservableNumberValue op1, ObservableNumberValue op2)

总的来说,这为相对简单的操作生成了大量的方法——36 个实用方法用于 4 个基本操作。

这可能看起来有点过火,但它确实做到了它应该做的。JavaFX 的创建者为每个基本的数学运算提供了实现,这样您就不必做这些跑腿工作了。

选择最大或最小

和往常一样,JavaFX 在定义方便的方法方面做了大量的跑腿工作。下面是 max() 方法,它们在可观察值和非可观察值之间进行交换和更改。

max(double op1, ObservableNumberValue op2)
max(ObservableNumberValue op1, double op2)
max(ObservableNumberValue op1, ObservableNumberValue op2)

事实上,当你需要求两个数的最小值时,这是完全一样的。绑定总是等于两个的最小值——至少其中一个是可观察的。

min(double op1, ObservableNumberValue op2)
min(ObservableNumberValue op1, double op2)
min(ObservableNumberValue op1, ObservableNumberValue op2)

比较值(=, !=, <, >, <=, >=)

当列表已满,选择了足够多的道具,或者当他们在游戏中获胜或失败时,价值比较可以自动提醒用户。

取负和取反

在最简单的情况下,JavaFX 提供了计算负数(减去 1 乘以数字)和的方法:

negate(ObservableNumberValue value)
not(ObservableBooleanValue op)

不等于

在复杂性方面,JavaFX 还提供了两种不同类型对象之间的比较。绑定的值将始终报告这两个对象是否相等。

notEqual(double op1, ObservableNumberValue op2, double epsilon)
notEqual(Object op1, ObservableObjectValue<?> op2)
notEqual(ObservableNumberValue op1, double op2, double epsilon)
notEqual(ObservableNumberValue op1, ObservableNumberValue op2)
notEqual(ObservableNumberValue op1, ObservableNumberValue op2, double epsilon)
notEqual(ObservableObjectValue<?> op1, Object op2)
notEqual(ObservableObjectValue<?> op1, ObservableObjectValue<?> op2)

它还为比较字符串提供了很多支持。特别值得注意的是 notEqualIgnoreCase() 方法。在比较之前,我花了很长时间将所有的字符串转换成小写。

notEqual(ObservableStringValue op1, ObservableStringValue op2)
notEqual(ObservableStringValue op1, String op2)
notEqual(String op1, ObservableStringValue op2)
notEqualIgnoreCase(ObservableStringValue op1, ObservableStringValue op2)
notEqualIgnoreCase(ObservableStringValue op1, String op2)
notEqualIgnoreCase(String op1, ObservableStringValue op2)

正如标题所建议的,我们的值比较 bucket 还包含比较小于和大于的数值的方法。查看文档了解所有的细节——我暂时让您休息一下,然后继续讨论集合。

操作集合

Bindings API 提供了四种不同的绑定到集合的方式:复制、索引绑定、大小绑定和空绑定。只有第一个集合将集合的内容复制到目标集合中。其他三个提取值—单个变量—基于集合状态的一个方面。

绑定两个集合

要将两个集合绑定在一起,您可以调用 Bindings.bindContent()Bindings.bindContentBidirectional()

在第一种情况下,你将跟踪一个可观察集合——一个 ObservableList, ObservableSet 或 ObservableMap——并在同一类型的非可观察集合中创建一个 carbon-copy。

bindContent(List<E> list1, ObservableList<? extends E> list2)
bindContent(Map<K,V> map1, ObservableMap<? extends K,? extends V> map2)
bindContent(Set<E> set1, ObservableSet<? extends E> set2)

在这种情况下,非可观察对象集合的内容会立即被可观察对象的内容覆盖。

如果你想把两个可观察集合绑定在一起,使用 Bindings.bindContentBidirectional() 方法,它接受两个相同类型的集合作为参数。

bindContentBidirectional(ObservableList<E> list1, ObservableList<E> list2)
bindContentBidirectional(ObservableMap<K,V> map1, ObservableMap<K,V> map2)
bindContentBidirectional(ObservableSet<E> set1, ObservableSet<E> set2)

在这些情况下,第一个集合将被擦除,并作为绑定过程的一部分使用第二个列表的内容。

Bindings API 不支持任何更复杂的集合绑定——比如相互映射、范围受限的列表复制。如果需要这些,则需要创建自己的绑定。为此,请查看下面的低级API部分。

绑定集合——相关的值

除此之外,绑定的选项仅限于绑定与集合相关的值——特定索引处的值、集合的大小或集合是否为空。

将值绑定到集合中特定位置的对象

根据集合中指定索引处的单个值绑定变量是非常容易的。在每种情况下,你都需要提供一个可观察对象集合,以及该对象的索引。

索引的值也可以作为可观察值传入,这意味着随着它的变化,绑定的值也会发生变化。

每种对象类型都有一个方法专门化,它为请求的值生成正确类型的绑定对象。布尔值、浮点值、双精度值、整型值、长值和字符串值都通过单独的方法支持。

记住:绝对值得记住的是,这些方法不会像我们上面看到的那样把你的参数绑定在一起。每个 valueAt 方法都返回一个 Binding 对象,其中包含您的值。

绑定到大小或空置的

基于大小或集合是否为空的绑定要容易得多。绑定 API 提供了对 ObservableList, ObservableArray, ObservableSet 和 ObservableMap对象的支持,以及 ObservableStringValue。

查看 isEmpty() 和各种 size() 方法的文档,查看所有选项。

创建绑定对象

我将在这里介绍的 Bindings API 的最后一部分是绑定多个自定义对象,并提供一个函数来计算该值。

createBooleanBinding(Callable<Boolean> func, Observable... dependencies)
createDoubleBinding(Callable<Double> func, Observable... dependencies)
createFloatBinding(Callable<Float> func, Observable... dependencies)
createIntegerBinding(Callable<Integer> func, Observable... dependencies)
createLongBinding(Callable<Long> func, Observable... dependencies)
createObjectBinding(Callable<T> func, Observable... dependencies)
createStringBinding(Callable<String> func, Observable... dependencies)

这几乎和低级 API 一样好,但它有一些限制:

请注意,它依赖于虚方法调用(您正在传递一个函数对象,而不是创建一个类方法)。这意味着如果你经常使用它,它可能会比低层 API 慢。

不过,这是一个相对简单的API——只需传入每个依赖项,然后使用提供的函数对它们进行转换。为了清晰起见,我将函数定义为 lambda,但您也可以手动创建 Callable<Double> 并覆盖 call() 方法。


IntegerProperty cost = new SimpleIntegerProperty(15);
DoubleProperty multiplier = new SimpleDoubleProperty(25);
double flatRate = 4;
DoubleBinding totalCost = Bindings.createDoubleBinding(
        () -> cost.get() * multiplier.get() + flatRate,
        cost, multiplier
);

如果这不能满足您的需求,您可以通过扩展 JavaFX 的 Bindings 类中可用的对象来创建完全定制的绑定。这样可以更快。所以如果你需要,这里是如何做的。

高阶绑定——低级 API Binding

在 JavaFX 中创建绑定的最可定制的方法是自己手动创建一个 binding 对象。这样做的好处是,您可以精确地定义所需的计算,而不必创建 Expression 对象链,这可能会降低性能。

当然,您也可以进行复杂的计算并使用 Bindings API 绑定多个对象,但正如我们前面看到的那样,这样做的效率并不高。低级 API 的好处是,在计算值时需要执行的任何计算都是在自定义 Binding 类中定义的。

如果你想知道为什么这可能会有更好的性能,那就是类函数更有可能被编译器“内联”。这意味着,如果您需要一个绑定的值重复且快速地为计算机,那么您的代码可能会执行得更快。

什么是低级 API

低级 API, 10 个抽象绑定类的集合,旨在实现所有棘手的绑定(例如,添加和删除侦听器)。这使您可以集中精力指定应该如何计算绑定的值。

每个类都接受可观察值(比如属性)并将它们转换为输出。就像 Fluent 和 Bindings API 一样,低级 API 支持布尔值、字符串、数字、集合和对象。

创建低级 Binding

创建低级绑定可以像定义抽象内部类(与其他代码一起定义的类)一样简单。因为抽象绑定类只有一个抽象方法,所以您只需要在定义方法时重写 computeValue()

在定义绑定时,官方建议在绑定创建期间使用初始化块绑定源属性。老实说,与创建构造函数相比,我不喜欢这样做,但这可能只是因为初始化块看起来有点陌生。

总体效果是完全相同的——编译器将代码从初始化块复制到每个构造函数中。如果您要创建一个将要使用多次的具体类,则构造函数方法更合适。

		//Inside your binding object at the top
    {
        super.bind(cost, itemCount);
    }

然后,剩下的工作就是定义 computeValue() 方法。在本例中,它非常简单,但是您可以将计算变得任意复杂。

DoubleProperty cost = new SimpleDoubleProperty(25);
IntegerProperty itemCount = new SimpleIntegerProperty(15);
DoubleBinding totalCost = new DoubleBinding() {
    
    {
        super.bind(cost, itemCount);
    }
    
    @Override
    protected double computeValue() {
        return itemCount.get() * cost.get();
    }
};

由此可见,totalCost 绑定的值将始终反映 cost 和 itemCount 属性的乘积。

如果您希望能够传递 totalCost 对象并在稍后检索依赖项,您可以添加额外的功能来覆盖默认的 getDependencies() 方法。

给低级 API 添加功能

在可定制的 value 方法之上,可以通过覆盖 getDependencies()dispose() 方法来扩展低级 API 中的每个类。

  • getDependencies():如果您需要存储它们并在以后取回它们,可以返回所有的依赖项(见下面的警告!)
  • dispose():可以注销绑定所有依赖项的侦听器。您通常不需要这样做,除非您特别在没有弱监听器(默认)的情况下实现绑定。

重写 getDependencies()

如果您希望能够将绑定对象传递给另一个类,或者存储它并在以后检索依赖项,那么重写 getDependencies() 是很有用的。

DoubleBinding totalCost = new DoubleBinding() {
    {
        super.bind(cost, itemCount);
    }
    @Override
    protected double computeValue() {
        return itemCount.get() * cost.get();
    }
    @Override
    public ObservableList<?> getDependencies() {
        return FXCollections.observableList(Arrays.asList(cost, itemCount));
    }
};

在匆忙重写此方法之前,值得记住的是,低级API的所有默认实现都使用弱侦听器。这意味着:

如果你使用默认实现的低级 API,你需要保持对你的可观察对象的强引用,否则它们将被垃圾收集,引用将丢失。

也就是说,如果您已经用强侦听器实现了绑定,那么您还需要重写 dispose() 方法。这将防止内存泄漏的发生,如果绑定被使用并被遗忘(至少被您……)后没有从被观察的对象中注销。

重写 dispose()

重写 dispose()方法就像系统地注销我们开始时绑定的每个可观察对象一样简单。要在一次调用中完成此操作,可以调用 unbind(),传入每个值。

@Override
public void dispose() {
    unbind(cost, itemCount);
}

如果您有一个更复杂的自定义实现,您可能需要一次查找和取消注册一个可观察对象。

总结

JavaFX 中的属性可以是只读的,也可以是可写的,但是始终是可观察的。每个属性都实现了 javafx.bean 的功能。绑定,javafx.beans.value 和 javafx.beans.property 包。

每个属性都可以使用 InvalidationListener 或 ChangeListener 对象来观察。这两个都可以通过调用 addListener() 方法来访问,因为每个属性都有一个 addListener() 方法来进行无效和更改。

属性监听的一个扩展是属性绑定,该功能允许用户将属性连接在一起,以便根据一个或多个更改自动更新属性。

在此基础上,JavaFX 支持通过 Expression 对象和 bindings 对象扩展绑定。通过 Fluent 和 Bindings api 访问这些内容是最简单的。但是,如果您绝对需要性能或定制,那么低级 API 允许您自己创建完全定制的绑定。