You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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;
    }
}

问题根源

  1. 状态保存时机不完整:saveState()可能仅在部分输入框变更时被调用(比如最后一个文本框),导致栈中只记录了该输入框的操作历史,其他输入框的变更未被保存,无法多步撤销。
  2. 栈缺少初始状态:应用启动时没有将所有输入框为0的初始状态推入栈,第一次撤销后栈为空,后续撤销无状态可恢复。
  3. undo方法逻辑漏洞:允许将栈弹空,导致prevState为null,调用restoreState时会触发空指针异常,且无状态可恢复。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.04 13:38:17