我在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); }大概
newValue是
null和
nullNPE
的自动拆箱。由于输入来自编辑器,因此我怀疑
IntegerStringConverter哪个是默认转换器。
在这里查看实现:
javafx.util.converter.IntegerStringConverter
public class IntegerStringConverter extends StringConverter<Integer> { @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); } @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));}那么行为将是一致的,并使编辑者有机会在输入值工厂之前对其进行处理。



