当前位置: 首页 > 面试题库 >

JavaFX Spinner空文本nullpointerexception

谢泽语
2023-03-14
问题内容

我有一个问题,如果清除了编辑器文本并提交然后单击“递增”或“递减”按钮,则可编辑的JavaFX 8
Spinner会导致无法捕获NullPointerException。这是 j8u60
j8u77。运气好的话,递增/递减按钮将陷入按下状态,并且NPE不断流动,从而锁定了应用程序

以下代码为我重现了该问题:

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Spinner;
import javafx.scene.control.SpinnerValueFactory;
import javafx.scene.control.SpinnerValueFactory.IntegerSpinnerValueFactory;
import javafx.stage.Stage;

public class Test extends Application {
    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage aPrimaryStage) throws Exception {
        IntegerSpinnerValueFactory valueFactory = new IntegerSpinnerValueFactory(0, 10);
        Spinner<Integer> spinner = new Spinner<>(valueFactory);
        spinner.setEditable(true);
        aPrimaryStage.setScene(new Scene(spinner));
        aPrimaryStage.show();
    }
}

运行它,清除文本,按Enter(NullPointerException),单击增量或减量按钮现在也会导致NPE。

谁能确认这是JavaFX错误并提出解决方法?

编辑:异常堆栈跟踪

Exception in thread "JavaFX Application Thread" java.lang.NullPointerException
    at javafx.scene.control.SpinnerValueFactory$IntegerSpinnerValueFactory.lambda$new$215(SpinnerValueFactory.java:475)
    at com.sun.javafx.binding.ExpressionHelper$Generic.fireValueChangedEvent(ExpressionHelper.java:361)
    at com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:81)
    at javafx.beans.property.ObjectPropertyBase.fireValueChangedEvent(ObjectPropertyBase.java:105)
    at javafx.beans.property.ObjectPropertyBase.markInvalid(ObjectPropertyBase.java:112)
    at javafx.beans.property.ObjectPropertyBase.set(ObjectPropertyBase.java:146)
    at javafx.scene.control.SpinnerValueFactory.setValue(SpinnerValueFactory.java:150)
    at javafx.scene.control.Spinner.lambda$new$210(Spinner.java:139)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:86)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8411)
    at com.sun.javafx.scene.control.behavior.TextFieldBehavior.fire(TextFieldBehavior.java:179)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callAction(TextInputControlBehavior.java:178)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.callActionForEvent(BehaviorBase.java:218)
    at com.sun.javafx.scene.control.behavior.TextInputControlBehavior.callActionForEvent(TextInputControlBehavior.java:127)
    at com.sun.javafx.scene.control.behavior.BehaviorBase.lambda$new$74(BehaviorBase.java:135)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventHandlerRecord.handleBubblingEvent(CompositeEventHandler.java:218)
    at com.sun.javafx.event.CompositeEventHandler.dispatchBubblingEvent(CompositeEventHandler.java:80)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:238)
    at com.sun.javafx.event.EventHandlerManager.dispatchBubblingEvent(EventHandlerManager.java:191)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchBubblingEvent(CompositeEventDispatcher.java:59)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:58)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:49)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Node.fireEvent(Node.java:8411)
    at com.sun.javafx.scene.control.skin.SpinnerSkin.lambda$new$473(SpinnerSkin.java:151)
    at com.sun.javafx.event.CompositeEventHandler$NormalEventFilterRecord.handleCapturingEvent(CompositeEventHandler.java:282)
    at com.sun.javafx.event.CompositeEventHandler.dispatchCapturingEvent(CompositeEventHandler.java:98)
    at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:223)
    at com.sun.javafx.event.EventHandlerManager.dispatchCapturingEvent(EventHandlerManager.java:180)
    at com.sun.javafx.event.CompositeEventDispatcher.dispatchCapturingEvent(CompositeEventDispatcher.java:43)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:52)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.BasicEventDispatcher.dispatchEvent(BasicEventDispatcher.java:56)
    at com.sun.javafx.event.EventDispatchChainImpl.dispatchEvent(EventDispatchChainImpl.java:114)
    at com.sun.javafx.event.EventUtil.fireEventImpl(EventUtil.java:74)
    at com.sun.javafx.event.EventUtil.fireEvent(EventUtil.java:54)
    at javafx.event.Event.fireEvent(Event.java:198)
    at javafx.scene.Scene$KeyHandler.process(Scene.java:3964)
    at javafx.scene.Scene$KeyHandler.access$1800(Scene.java:3910)
    at javafx.scene.Scene.impl_processKeyEvent(Scene.java:2040)
    at javafx.scene.Scene$ScenePeerListener.keyEvent(Scene.java:2501)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:197)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler$KeyEventNotification.run(GlassViewEventHandler.java:147)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.lambda$handleKeyEvent$353(GlassViewEventHandler.java:228)
    at com.sun.javafx.tk.quantum.QuantumToolkit.runWithoutRenderLock(QuantumToolkit.java:389)
    at com.sun.javafx.tk.quantum.GlassViewEventHandler.handleKeyEvent(GlassViewEventHandler.java:227)
    at com.sun.glass.ui.View.handleKeyEvent(View.java:546)
    at com.sun.glass.ui.View.notifyKey(View.java:966)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$148(WinApplication.java:191)
    at java.lang.Thread.run(Thread.java:745)

问题答案:

我在JDK源代码中流连忘返。

NPE从下面if (newValue < getMin()) {的侦听器lambda中抛出:

javafx.scene.control.SpinnerValueFactory.java

    public IntegerSpinnerValueFactory(@NamedArg("min") int min,
                                      @NamedArg("max") int max,
                                      @NamedArg("initialValue") int initialValue,
                                      @NamedArg("amountToStepBy") int amountToStepBy) {
        setMin(min);
        setMax(max);
        setAmountToStepBy(amountToStepBy);
        setConverter(new IntegerStringConverter());

        valueProperty().addListener((o, oldValue, newValue) -> {
            // when the value is set, we need to react to ensure it is a
            // valid value (and if not, blow up appropriately)
            if (newValue < getMin()) { 
                setValue(getMin());
            } else if (newValue > getMax()) {
                setValue(getMax());
            }
        });
        setValue(initialValue >= min && initialValue <= max ? initialValue : min);
    }

大概newValuenullnullNPE
的自动拆箱。由于输入来自编辑器,因此我怀疑IntegerStringConverter哪个是默认转换器。

在这里查看实现:

javafx.util.converter.IntegerStringConverter

public class IntegerStringConverter extends StringConverter<Integer> {
    /** {@inheritDoc} */
    @Override public Integer fromString(String value) {
        // If the specified value is null or zero-length, return null
        if (value == null) {
            return null;
        }

        value = value.trim();

        if (value.length() < 1) {
            return null;
        }

        return Integer.valueOf(value);
    }

    /** {@inheritDoc} */
    @Override public String toString(Integer value) {
        // If the specified value is null, return a zero-length String
        if (value == null) {
            return "";
        }

        return (Integer.toString(((Integer)value).intValue()));
    }
}

我们看到它将很高兴地返回null空字符串,考虑到输入不存在有效值,这是合理的。

跟踪调用堆栈,我发现值从何而来:

javafx.scene.control.Spinner

public Spinner() {
    getStyleClass().add(DEFAULT_STYLE_CLASS);
    setAccessibleRole(AccessibleRole.SPINNER);

    getEditor().setOnAction(action -> {
        String text = getEditor().getText();
        SpinnerValueFactory<T> valueFactory = getValueFactory();
        if (valueFactory != null) {
            StringConverter<T> converter = valueFactory.getConverter();
            if (converter != null) {
                T value = converter.fromString(text);
                valueFactory.setValue(value);
            }
        }
    });

用从转换器获得的值设置该值,该值T value = converter.fromString(text);可能为空。在这一点上,我相信Spinner类应该检查value是否存在null,是否将先前的值恢复到编辑器。

我现在相当确定这是一个错误。此外,我认为使用永远不会返回null的转换器来解决问题不会正常工作,因为它只会掩盖问题,并且在无法转换值时应返回什么值?

onAction用“返回有效”策略替换微调框编辑器的拒绝无效输入可解决此问题:

public static <T> void fixSpinner2(Spinner<T> aSpinner) {
    aSpinner.getEditor().setOnAction(action -> {
        String text = aSpinner.getEditor().getText();
        SpinnerValueFactory<T> factory = aSpinner.getValueFactory();
        if (factory != null) {
            StringConverter<T> converter = factory.getConverter();
            if (converter != null) {
                T value = converter.fromString(text);
                if (null != value) {
                    factory.setValue(value);
                }
                else {
                    aSpinner.getEditor().setText(converter.toString(factory.getValue()));
                }
            }
        }
        action.consume();
    });
}

与侦听器相反,valueProperty这避免了使用无效数据触发其他侦听器。但是,这突出了旋转器类中的另一个问题。上面的方法通过按Enter返回有效值来解决问题。删除输入而不提交(按Enter),然后按递增或递减将导致相同的NPE,但调用堆栈略有不同。

原因:

public void increment(int steps) {
    SpinnerValueFactory<T> valueFactory = getValueFactory();
    if (valueFactory == null) {
        throw new IllegalStateException("Can't increment Spinner with a null SpinnerValueFactory");
    }
    commitEditorText();
    valueFactory.increment(steps);
}

减量类似,两者都称为commitEditorText

private void commitEditorText() {
    if (!isEditable()) return;
    String text = getEditor().getText();
    SpinnerValueFactory<T> valueFactory = getValueFactory();
    if (valueFactory != null) {
        StringConverter<T> converter = valueFactory.getConverter();
        if (converter != null) {
            T value = converter.fromString(text);
            valueFactory.setValue(value);
        }
    }
}

注意onAction构造器中的复制粘贴:

    getEditor().setOnAction(action -> {
        String text = getEditor().getText();
        SpinnerValueFactory<T> valueFactory = getValueFactory();
        if (valueFactory != null) {
            StringConverter<T> converter = valueFactory.getConverter();
            if (converter != null) {
                T value = converter.fromString(text);
                valueFactory.setValue(value);
            }
        }
    });

我相信commitEditorText应该将其更改为onAction在编辑器上触发,如下所示:

private void commitEditorText() {
    if (!isEditable()) return;
    getEditor().getOnAction().handle(new ActionEvent(this, this));
}

那么行为将是一致的,并使编辑者有机会在输入值工厂之前对其进行处理。



 类似资料:
  • 我正在使用javax.mail-1.6.2.jar,我制作了一个外部使用的类,我想在其中发送邮件。主要问题在代码的这一部分: 在这个配置中,我在电子邮件中收到文本,因为我覆盖了mime内容。如果我删除行,我的邮件将保持为空。所以contetnt类型和消息变量完全正常,只有Multipart无法正常工作。我在调试时看到,目前我设置了body part的内容,但内容为空,并且dh(datahandle

  • 问题内容: 我一直在阅读和谷歌搜索,但似乎找不到这个简单的答案。 我有一个读取文件的功能,但是如果文件不存在,则会出现混乱。我想做的是一个函数,该函数在读取之前会检查文件是否存在,如果不存在,则会创建一个空文件。这就是我所拥有的。 问题答案: 不要尝试先检查是否存在,因为如果同时创建了文件,那么您将面临一场竞赛。您可以打开带有标志的文件以创建该文件(如果不存在):

  • 我遇到JavaFX文本格式化程序的问题。我想知道是我做错了什么,还是JavaJDK有错误。 以下是显示问题的代码: 单击按钮几次。第一次按预期工作输出是:newControlText=test0 newControlText=newControlText=test 第二次和每一次尝试都会导致异常: 您能帮助我了解问题在哪里(我的代码或JDK)?

  • 我已确认已在服务器上安装gettext。我使用本教程开始学习,并遵循其指导。 我还在PHP手册的gettext()页面上读到,如果在翻译表中找不到翻译,它将只使用未翻译的文本。 对于本地化,我有 我有两个目录设置,一个为en(翻译/en/LC_MESSAGES/message.po)和另一个为zh(相同的结构。)我生成的. po文件使用 我的问题:根本没有显示“en”的文本原始字符串是英文的,我最

  • 我正试图从一组PDF文件中提取一些信息。这是目前为止的工作,但一个PDF给我的委屈。 我使用的是PDFBOX1.8.8和Java7。 我没有例外,什么都没有。有什么想法吗? 编辑:附加信息:使用Acrobat Distiller 9.0.0(Windows)创建,格式为PDF-1.6;其他PDF是版本1.4和1.5 似乎并不包含异国情调的角色。我可以在Evince PDF-viewer中标记/复制

  • 以下是我在form1构造函数中使用文本文件的方式: 创建空文本文件: 这是文本文件test.txt在所有提取之后的最终结果: 文本文件 你可以看到第一行是空行,然后最大的文本行不是从左边开始的,而是从左边开始的,但是有一个空格。然后在每两行之间有一个空格/空行。