JFormattedTextField.isEditValid()负数校验返回值异常问题
程序中为JTable实现了基于JFormattedTextField的自定义单元格编辑器,业务逻辑依赖JFormattedTextField的isEditValid()方法返回值判断是否执行后续数据处理,该方法返回结果的准确性直接决定功能是否正常。
输入相同负数值时,isEditValid()返回结果不稳定:部分环境返回true,部分环境返回false。初期怀疑是Java版本差异导致——Corretto 11版本下返回false,Oracle JDK 1.8版本下返回true。返回false的异常场景中,按下Enter键无法触发编辑结束,必须点击单元格外部区域才会触发生成输出日志。
复现代码如下:
import java.awt.*; import java.math.RoundingMode; import java.text.NumberFormat; import javax.swing.*; import javax.swing.table.*; import javax.swing.text.DefaultFormatterFactory; import javax.swing.text.NumberFormatter; class SimpleTest extends JFrame { private JPanel topPanel; private JTable table; private JScrollPane scrollPane; private String[] columnNames=new String[3]; private String[][] dataValues=new String[3][3]; public SimpleTest() { setTitle("JTable Cell Validation"); setSize(300,300); setDefaultCloseOperation (EXIT_ON_CLOSE); topPanel=new JPanel(); topPanel.setLayout(new BorderLayout()); getContentPane().add(topPanel); columnNames=new String[] {"Name" ,"Value"}; dataValues=new String[][] { {"DummyField ","0"} }; TableModel model=new myTableModel(); table=new JTable(); table.setModel(model); table.getColumnModel().getColumn(1).setCellEditor(new MyCustomerEditor()); table.setCellSelectionEnabled(true); scrollPane=new JScrollPane(table); topPanel.add(scrollPane,BorderLayout.CENTER); } public class myTableModel extends DefaultTableModel { myTableModel() { super(dataValues,columnNames); } public boolean isCellEditable(int row,int cols) { if (cols == 0) return false; return true; } } public static void main(String args[]) { SimpleTest x=new SimpleTest(); x.setVisible(true); } public class MyCustomerEditor extends DefaultCellEditor { private static final long serialVersionUID = 1L; int attrLength; public MyCustomerEditor() { super(new JFormattedTextField()); System.out.println("Inside the editor."); final NumberFormat integerFormat = NumberFormat.getIntegerInstance(); integerFormat.setGroupingUsed(false); integerFormat.setRoundingMode(RoundingMode.DOWN); integerFormat.setParseIntegerOnly(true); final NumberFormatter intFormatter = new NumberFormatter(integerFormat); intFormatter.setOverwriteMode(false); final JFormattedTextField formattedTextField = (JFormattedTextField) getComponent(); formattedTextField.setFormatterFactory(new DefaultFormatterFactory(intFormatter)); formattedTextField.setHorizontalAlignment(SwingConstants.RIGHT); formattedTextField.setFocusLostBehavior(JFormattedTextField.PERSIST); } @Override public boolean stopCellEditing() { System.out.println("Stopped editing"); if (processData()) { return false; } return super.stopCellEditing(); } boolean processData() { final JFormattedTextField textField = (JFormattedTextField) super.getComponent(); final Object o = super.getCellEditorValue(); final String integer = o.toString(); System.out.println("Processing Integer value : " + integer); System.out.println(" textField.getText() : " + textField.getText()); System.out.println(" textField.isEditValid() : " + textField.isEditValid()); return true; } } }
最初因3个测试环境安装的Java版本不一致,怀疑是JDK版本差异导致;后续经测试排除版本因素,确认问题根源是区域设置(Locale)差异:同一台机器仅通过-D启动参数修改Locale配置,即可复现正常/异常两种表现。
1. 修复Locale导致的JFormattedTextField负数校验异常
问题出在初始化NumberFormat时没有指定固定Locale,默认会加载JVM当前继承的系统区域配置,不同Locale对负号的识别规则、合法负数格式要求存在差异:部分Locale仅识别本地语言的负号标记、不识别半角-,或是对负号的位置、前后空格有特殊要求,就会把合法的半角负数输入判定为无效。
直接在初始化NumberFormat时强制指定固定Locale即可,比如业务要求统一支持半角负号、阿拉伯数字输入,就指定Locale.ROOT:
// 替换原代码中无参的getIntegerInstance()调用 final NumberFormat integerFormat = NumberFormat.getIntegerInstance(Locale.ROOT);
如果需要适配不同区域用户的输入习惯,可以在校验前先做字符归一化,把全角负号、各区域本地负号统一替换为半角-,再交给Formatter处理。
另外当前代码中processData()方法固定返回true,会导致stopCellEditing()永远返回false,这也是按Enter键无法正常结束编辑的直接诱因,修复校验逻辑后需要调整返回值:输入合法时返回false,让编辑停止流程能正常执行。
2. 不受Locale影响的替代数值校验方案
不要把isEditValid()作为唯一的校验依据,推荐两种稳定性更高的方案:
- 自定义正则+手动类型转换校验:拿到输入文本后,先用正则匹配业务要求的数值格式(比如支持负整数就用
^-?\d+$),匹配通过后手动调用Integer.parseInt()/Long.parseLong()做类型转换,不抛出NumberFormatException就判定为合法。整个逻辑完全自主可控,不会随Locale配置变化。 - 替换Formatter实现:用
MaskFormatter替代NumberFormatter,通过写死输入掩码限定合法格式(比如允许最多6位整数、可选前置负号就设置掩码为"#######"),掩码匹配规则是固定的,不受区域配置影响。
内容的提问来源于stack exchange,提问作者Unhandled Exception

