Java 为什么在绑定中调用绑定值的get会引发stackoverflow错误

Java 为什么在绑定中调用绑定值的get会引发stackoverflow错误,java,javafx,binding,stack-overflow,bind,Java,Javafx,Binding,Stack Overflow,Bind,当获取要绑定的标签的值时,此代码在标签绑定中抛出StackOverflower错误。我希望标签最初是“test”,然后第一次按“test pressed”,然后按“test pressed”,依此类推。但是,读取该值会抛出StackOverflowerError,因为调用getText()方法会触发绑定。我期望只有按钮按下事件触发绑定 注意:我已经注释掉了导致错误的代码,并添加了另一个按钮,以更好地显示我的困惑 import javafx.application.Application; imp

当获取要绑定的标签的值时,此代码在标签绑定中抛出StackOverflower错误。我希望标签最初是“test”,然后第一次按“test pressed”,然后按“test pressed”,依此类推。但是,读取该值会抛出StackOverflowerError,因为调用getText()方法会触发绑定。我期望只有按钮按下事件触发绑定

注意:我已经注释掉了导致错误的代码,并添加了另一个按钮,以更好地显示我的困惑

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;

public class Main extends Application{

    @Override
    public void start(Stage primaryStage) {

        Label l = new Label("test");
        Button b = new Button("press me");

        l.textProperty().bind(Bindings.createStringBinding(() ->{
            System.out.println("changing label text");
            return "ok";
            //return l.getText() + " pressed"; //Causes a stackoverflow error
        },b.pressedProperty()));

        Button b2 = new Button("press me 2");
        b2.pressedProperty().addListener((o) -> {
            l.getText(); //Why does this not triggger the binding?
        });

        VBox root = new VBox();
        root.getChildren().addAll(l,b,b2);

        Scene scene = new Scene(root, 300, 250);
        primaryStage.setTitle("Binding test");
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    public static void main(String[] args){
        launch(args);
    }
}
我在这里的目标是要有一个在一定条件下不会改变案文的约束力。可调用Lambda中的逻辑类似于:

if(condition){
    return "ok";
}else{
    return l.getText(); //if the condition is not met then use the current value.
}
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed()) {
         return "test pressed";
    } else {
         return "test";
    }
}, b.pressedProperty());

我知道我可以在pressed属性上使用侦听器,然后以这种方式设置文本标签,所以我有一个解决方案,但我想知道为什么会发生上述情况

就语义而言,您的绑定表达了这样一条规则:标签的文本是与
“pressed”
连接的标签文本。显然,这意味着标签的文本依赖于标签的文本,因此它是递归的

我认为这不是你想强加的规则。我想你想要这个 规则为“未按下按钮时标签文本为
”测试“
,按下按钮时标签文本为
”测试“
。(现在,如果按钮的
按下属性发生变化,绑定被告知重新计算,但值实际上并不取决于该属性。)

从技术上讲,正在发生的事情大致如下:

public class Label {

    private final StringProperty textProperty = new SimpleStringProperty() ;

    public String getText() {
        return textProperty().get();
    }

    // ...
}
public abstract class StringBinding {

    private boolean valid = false;
    private String value ;

    protected void bind(ObservableStringValue dependency) {
        dependency.addListener(o -> invalidate());
    }

    private void invalidate() {
        valid = false ;
        // notify invalidation listeners...
    }

    public String get() {
        if (!valid) {
            value = computeValue();
            valid = true ;
        }
        return value ;
    }

    public abstract String computeValue();
}

最后,字符串绑定具有以下逻辑:

public class Label {

    private final StringProperty textProperty = new SimpleStringProperty() ;

    public String getText() {
        return textProperty().get();
    }

    // ...
}
public abstract class StringBinding {

    private boolean valid = false;
    private String value ;

    protected void bind(ObservableStringValue dependency) {
        dependency.addListener(o -> invalidate());
    }

    private void invalidate() {
        valid = false ;
        // notify invalidation listeners...
    }

    public String get() {
        if (!valid) {
            value = computeValue();
            valid = true ;
        }
        return value ;
    }

    public abstract String computeValue();
}
在您的示例中,
computeValue()
的实现调用标签的
getText()
方法

因此,当您创建绑定时,标签文本属性的值是根据绑定的值设置的。绑定无效(因为尚未计算),因此它是通过您提供的方法计算的。该方法调用
label.getText()
,它从属性获取值。由于属性已绑定,它会检查绑定,该绑定仍然无效(因为其值的计算尚未完成),因此它会计算其值,从而调用
label.getText()

因此,您可能需要以下内容:

if(condition){
    return "ok";
}else{
    return l.getText(); //if the condition is not met then use the current value.
}
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed()) {
         return "test pressed";
    } else {
         return "test";
    }
}, b.pressedProperty());
如果希望基础字符串能够更改,则需要为其创建新属性:

StringProperty text = new SimpleStringProperty("test");
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed)() {
        return text.get() + " pressed" ;
    } else {
        return text.get();
    }
}, text, b.pressedProperty());
或者,相当于

label.textProperty().bind(text.concat(
    Bindings.when(b.pressedProperty())
    .then(" pressed")
    .otherwise("")));

就语义而言,您的绑定表达了这样一条规则:标签的文本是与
“pressed”
连接的标签文本。显然,这意味着标签的文本取决于标签的文本,因此它是递归的

我不认为这是你想要强加的规则。我认为你想要 如果未按下按钮,则规则为“标签文本为
”测试“
,如果按下按钮,则规则为“标签文本为
”测试“
。(现在,如果按钮的
pressed
属性发生更改,绑定将被告知重新计算,但该值实际上并不依赖于该属性。)

从技术上讲,正在发生的事情大致如下:

public class Label {

    private final StringProperty textProperty = new SimpleStringProperty() ;

    public String getText() {
        return textProperty().get();
    }

    // ...
}
public abstract class StringBinding {

    private boolean valid = false;
    private String value ;

    protected void bind(ObservableStringValue dependency) {
        dependency.addListener(o -> invalidate());
    }

    private void invalidate() {
        valid = false ;
        // notify invalidation listeners...
    }

    public String get() {
        if (!valid) {
            value = computeValue();
            valid = true ;
        }
        return value ;
    }

    public abstract String computeValue();
}

最后,字符串绑定具有以下逻辑:

public class Label {

    private final StringProperty textProperty = new SimpleStringProperty() ;

    public String getText() {
        return textProperty().get();
    }

    // ...
}
public abstract class StringBinding {

    private boolean valid = false;
    private String value ;

    protected void bind(ObservableStringValue dependency) {
        dependency.addListener(o -> invalidate());
    }

    private void invalidate() {
        valid = false ;
        // notify invalidation listeners...
    }

    public String get() {
        if (!valid) {
            value = computeValue();
            valid = true ;
        }
        return value ;
    }

    public abstract String computeValue();
}
在您的示例中,
computeValue()
的实现调用标签的
getText()
方法

因此,在创建绑定时,标签的文本属性的值是根据绑定的值设置的。绑定无效(因为尚未计算该绑定),因此它是通过您提供的方法计算的。该方法调用
label.getText()
,它从属性中获取值。由于属性已绑定,因此它会检查绑定,该绑定仍然无效(因为其值的计算尚未完成),因此它会计算其值,从而调用
label.getText()

因此,您可能需要以下内容:

if(condition){
    return "ok";
}else{
    return l.getText(); //if the condition is not met then use the current value.
}
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed()) {
         return "test pressed";
    } else {
         return "test";
    }
}, b.pressedProperty());
如果希望基础字符串能够更改,则需要为其创建新属性:

StringProperty text = new SimpleStringProperty("test");
label.textProperty().bind(Bindings.createStringBinding(() -> {
    if (b.isPressed)() {
        return text.get() + " pressed" ;
    } else {
        return text.get();
    }
}, text, b.pressedProperty());
或者,相当于

label.textProperty().bind(text.concat(
    Bindings.when(b.pressedProperty())
    .then(" pressed")
    .otherwise("")));

您的绑定表达了这样一条规则,即标签文本是与
“pressed”
连接的标签文本。。。它是无限的recursive@James_D我知道逻辑是递归的,但是触发递归的条件应该是Bindings.createStringBinding()绑定到的可观察值(b.pressed),而不是标签的值。但是绑定的值是什么?基本上,当计算绑定的值时,您必须计算标签的
label.getText()
,它查看标签的
textProperty()
的值。该值在无效后尚未设置(因为它仍在设置过程中),因此要获得该值,必须计算绑定。绑定的值应为l.getText()+“pressed”。我希望该值绑定到pressed属性,而不是它本身。是的,但是
l.getText()
返回什么?您的绑定表示标签文本是标签文本与
“pressed”
连接的规则。。。它是无限的recursive@James_D我知道逻辑是递归的,但是触发递归的条件应该是Bindings.createStringBinding()绑定到的可观察值(b.pressed),而不是标签的值。但是绑定的值是什么?基本上,当计算绑定的值时,您必须计算标签的
label.getText()
,它查看标签的
textProperty()
的值。该值在无效后尚未设置(因为它仍在设置过程中),因此要获得该值,必须计算绑定。绑定的值应为l.getText()+“pressed”。我希望值绑定到pressed属性,而不是它本身。是的,但是
l.getText()
返回什么?我想要的规则是