Java预算计算器多步撤销功能实现异常问题求助
预算计算器多步撤销功能失效问题排查与修复
我是Java编程新手,正在开发一款预算计算器应用,希望实现可连续执行多步撤销的功能,以撤销各输入框中输入的数值。目前代码仅支持单次撤销,连续点击撤销按钮仅能生效一次,无法进一步撤销。我发现UI中的最后一个文本框可正常实现连续撤销,因此怀疑栈的实现存在问题,但尚未找到解决办法。
涉及撤销功能的核心方法
public void saveState() { Map<String, Double> incomeValues = new HashMap<>(); Map<String, Double> spendingValues = new HashMap<>(); // 获取收入输入框当前值 for (Map.Entry<String, JTextField> entry : incomeFields.entrySet()) { String fieldName = entry.getKey(); JTextField field = entry.getValue(); String text = field.getText(); double value = text.isEmpty() ? 0 : Double.parseDouble(text); incomeValues.put(fieldName, value); } // 获取支出输入框当前值 for (Map.Entry<String, JTextField> entry : spendingFields.entrySet()) { String fieldName = entry.getKey(); JTextField field = entry.getValue(); String text = field.getText(); double value = text.isEmpty() ? 0 : Double.parseDouble(text); spendingValues.put(fieldName, value); } stateStack.push(new BudgetState(new HashMap<>(incomeValues), new HashMap<>(spendingValues))); } public void undo(int count) { while (count > 0 && !stateStack.isEmpty()) { stateStack.pop(); count--; } BudgetState prevState = stateStack.isEmpty() ? null : stateStack.peek(); restoreState(prevState); updateTotals(); } public void restoreState(BudgetState state) { Map<String, Double> incomeValues = state.getIncomeValues(); Map<String, Double> spendingValues = state.getSpendingValues(); // 给文本框赋值 for (Map.Entry<String, Double> entry : incomeValues.entrySet()) { String fieldName = entry.getKey(); double value = entry.getValue(); JTextField field = incomeFields.get(fieldName); field.setText(String.valueOf(value)); // 设置文本框值 } for (Map.Entry<String, Double> entry : spendingValues.entrySet()) { String fieldName = entry.getKey(); double value = entry.getValue(); JTextField field = spendingFields.get(fieldName); field.setText(String.valueOf(value)); // 设置文本框值 } }
BudgetState状态容器类
package Budget; import java.util.*; public class BudgetState { private Map<String, Double> incomeValues; private Map<String, Double> spendingValues; public BudgetState(Map<String, Double> incomeValues, Map<String, Double> spendingValues) { this.incomeValues = new HashMap<>(incomeValues); this.spendingValues = new HashMap<>(spendingValues); } public Map<String, Double> getIncomeValues() { return incomeValues; } public Map<String, Double> getSpendingValues() { return spendingValues; } }
问题根源
- 状态保存时机不完整:
saveState()可能仅在部分输入框变更时被调用(比如最后一个文本框),导致栈中只记录了该输入框的操作历史,其他输入框的变更未被保存,无法多步撤销。 - 栈缺少初始状态:应用启动时没有将所有输入框为0的初始状态推入栈,第一次撤销后栈为空,后续撤销无状态可恢复。
- undo方法逻辑漏洞:允许将栈弹空,导致
prevState为null,调用restoreState时会触发空指针异常,且无状态可恢复。 - restoreState未处理空状态:直接调用
state.getIncomeValues(),当state为null时会抛出异常。
修复方案
1. 给所有输入框添加变更监听,确保每步操作都保存状态
给所有收入和支出输入框添加DocumentListener,在内容变化时立即调用saveState():
// 工具方法:给输入框添加变更监听 private void addChangeListenerToTextField(JTextField textField) { textField.getDocument().addDocumentListener(new DocumentListener() { @Override public void insertUpdate(DocumentEvent e) { saveState(); } @Override public void removeUpdate(DocumentEvent e) { saveState(); } @Override public void changedUpdate(DocumentEvent e) { saveState(); } }); } // 初始化输入框时调用 for (JTextField field : incomeFields.values()) { addChangeListenerToTextField(field); } for (JTextField field : spendingFields.values()) { addChangeListenerToTextField(field); }
2. 初始化栈时存入初始状态
在应用初始化阶段,将空白状态推入栈:
// 初始化栈 stateStack = new Stack<>(); saveState(); // 保存所有输入框为0的初始状态
3. 修改undo方法,保留至少一个初始状态
public void undo(int count) { // 确保栈中至少保留一个初始状态,不能弹空 while (count > 0 && stateStack.size() > 1) { stateStack.pop(); count--; } // 此时栈一定不为空,直接取栈顶 BudgetState prevState = stateStack.peek(); restoreState(prevState); updateTotals(); }
4. 修复restoreState的空指针问题
增加空状态处理,同时优化空值显示:
public void restoreState(BudgetState state) { if (state == null) { // 恢复到初始空白状态 for (JTextField field : incomeFields.values()) { field.setText(""); } for (JTextField field : spendingFields.values()) { field.setText(""); } return; } Map<String, Double> incomeValues = state.getIncomeValues(); Map<String, Double> spendingValues = state.getSpendingValues(); for (Map.Entry<String, Double> entry : incomeValues.entrySet()) { String fieldName = entry.getKey(); double value = entry.getValue(); JTextField field = incomeFields.get(fieldName); // 值为0时显示空白,更符合用户习惯 field.setText(value == 0 ? "" : String.valueOf(value)); } for (Map.Entry<String, Double> entry : spendingValues.entrySet()) { String fieldName = entry.getKey(); double value = entry.getValue(); JTextField field = spendingFields.get(fieldName); field.setText(value == 0 ? "" : String.valueOf(value)); } }
内容的提问来源于stack exchange,提问作者user19573691
相关产品推荐
相关产品推荐

