杰瑞科技汇

Java JTable 如何刷新数据?

核心原理:JTable 与 TableModel 的关系

要理解 JTable 本身并不存储数据,它只是一个数据的“视图”或“显示组件”,真正存储数据和管理数据结构的是 TableModel(最常用的是其子类 DefaultTableModel)。

Java JTable 如何刷新数据?-图1
(图片来源网络,侵删)

当数据发生变化时,你需要告诉 TableModel 数据已经更新了,TableModel 会通知 JTable 重新绘制自身,这就是“刷新”的本质。

刷新的步骤:

  1. 获取或创建 TableModel:通常就是你的 JTable 使用的 table.getModel()
  2. 修改 TableModel 中的数据:添加行、删除行、修改单元格值等。
  3. 通知 JTable 数据已更改:这是最关键的一步,让视图知道需要更新。

简单刷新(推荐用于少量数据或整个数据集改变)

如果你的数据源是一个 DefaultTableModel,并且你想用全新的数据替换整个表格,最简单的方法是重新设置 TableModel

适用场景

Java JTable 如何刷新数据?-图2
(图片来源网络,侵删)
  • 数据量不大,重新创建模型开销小。
  • 表格的结构(列名)可能发生变化。
  • 需要完全清空表格并重新加载。

代码示例:

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class SimpleRefreshExample {
    private JFrame frame;
    private JTable table;
    private DefaultTableModel tableModel;
    public static void main(String[] args) {
        SwingUtilities.invokeLater(SimpleRefreshExample::new);
    }
    public SimpleRefreshExample() {
        // 1. 创建模型和表格
        String[] columnNames = {"ID", "Name", "Age"};
        Object[][] initialData = {
            {1, "Alice", 30},
            {2, "Bob", 25}
        };
        tableModel = new DefaultTableModel(initialData, columnNames);
        table = new JTable(tableModel);
        // 2. 创建界面
        frame = new JFrame("JTable Simple Refresh Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table), java.awt.BorderLayout.CENTER);
        JButton refreshButton = new JButton("Refresh Data");
        refreshButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 3. 刷新操作:创建新数据,替换整个模型
                Object[][] newData = {
                    {3, "Charlie", 35},
                    {4, "David", 40},
                    {5, "Eve", 28}
                };
                // 清空旧模型
                tableModel.setRowCount(0);
                // 添加新数据
                for (Object[] row : newData) {
                    tableModel.addRow(row);
                }
                // 注意:在这种情况下,setRowCount(0) 和 addRow 已经足够,
                // 但更彻底的方式是创建一个全新的模型并设置给 JTable
                // table.setModel(new DefaultTableModel(newData, columnNames));
            }
        });
        frame.add(refreshButton, java.awt.BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }
}

关键点

  • tableModel.setRowCount(0);:清空所有行。
  • tableModel.addRow(Object[] rowData);:向模型中添加一行。
  • table.setModel(newModel);:用一个全新的模型替换旧的,这会强制 JTable 完全重新绘制。

高效刷新(推荐用于数据量大或频繁更新)

当你只需要修改表格中的某一行或某一列的数据时,创建一个全新的 TableModel 会非常低效,尤其是在数据量大的情况下,这时,你应该直接修改现有模型中的数据,并通知视图更新。

适用场景

Java JTable 如何刷新数据?-图3
(图片来源网络,侵删)
  • 数据量大。
  • 频繁更新单行或单个单元格。
  • 表格结构(列名)保持不变。

核心方法

  • tableModel.setValueAt(Object aValue, int row, int column): 修改指定单元格的值。
  • tableModel.fireTableRowsUpdated(int firstRow, int lastRow): 手动触发行更新事件,这是高效刷新的关键!

代码示例:

import javax.swing.*;
import javax.swing.table.DefaultTableModel;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
public class EfficientRefreshExample {
    private JFrame frame;
    private JTable table;
    private DefaultTableModel tableModel;
    public static void main(String[] args) {
        SwingUtilities.invokeLater(EfficientRefreshExample::new);
    }
    public EfficientRefreshExample() {
        // 1. 创建模型和表格
        String[] columnNames = {"ID", "Name", "Score"};
        Object[][] initialData = {
            {1, "Player A", 100},
            {2, "Player B", 200},
            {3, "Player C", 150}
        };
        tableModel = new DefaultTableModel(initialData, columnNames);
        table = new JTable(tableModel);
        // 2. 创建界面
        frame = new JFrame("JTable Efficient Refresh Example");
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.add(new JScrollPane(table), java.awt.BorderLayout.CENTER);
        JButton updateButton = new JButton("Update Score");
        updateButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                // 3. 高效刷新操作:直接修改模型并通知
                // 模拟分数变化
                int playerRow = 1; // 更新第二行(Player B)
                int newScore = (int) tableModel.getValueAt(playerRow, 1) + 50; // 假设第二列是分数
                // 直接修改模型中的数据
                tableModel.setValueAt(newScore, playerRow, 1);
                // 手动触发更新事件,告诉JTable第1行(索引为1)的数据已经改变
                // 这样JTable只会重绘这一行,而不是整个表格,效率更高
                tableModel.fireTableRowsUpdated(playerRow, playerRow);
            }
        });
        frame.add(updateButton, java.awt.BorderLayout.SOUTH);
        frame.pack();
        frame.setVisible(true);
    }
}

关键点

  • setValueAt() 修改了内存中的数据,但不会自动让 JTable 重绘。
  • fireTableRowsUpdated()AbstractTableModel 的一个方法,它会创建一个 TableModelEvent 并通知所有注册的监听器(包括 JTable),从而触发特定行的重绘。
  • 其他类似的 fireXXX 方法:
    • fireTableDataChanged(): 所有数据都变了,需要完全重绘。
    • fireTableStructureChanged(): 表格结构(列名)变了,需要完全重绘。
    • fireTableRowsInserted(int firstRow, int lastRow): 插入了新行。
    • fireTableRowsDeleted(int firstRow, int lastRow): 删除了行。

绑定外部数据源(MVC 模式,最佳实践)

在实际项目中,你的数据通常不会直接存在 DefaultTableModel 中,而是存储在业务逻辑层(一个 List<User> 对象),这时,你应该创建一个自定义的 TableModel,让它作为你的数据源和 JTable 之间的桥梁。

适用场景

  • 遵循 MVC(Model-View-Controller)设计模式。
  • 数据来自数据库、网络或复杂的业务逻辑。
  • 需要更好的代码组织和可维护性。

步骤

  1. 创建一个类继承自 AbstractTableModel
  2. 在自定义模型中,持有一个对数据源(如 List)的引用。
  3. 实现 getRowCount(), getColumnCount(), getValueAt() 等方法,让它们从你的数据源中读取信息。
  4. 提供修改数据源的方法(如 addUser(), updateUser())。
  5. 在这些修改方法中,更新数据源,并调用 fireXXX 方法来通知 JTable。

代码示例:

import javax.swing.*;
import javax.swing.table.AbstractTableModel;
import java.util.ArrayList;
import java.util.List;
// 1. 自定义 TableModel
class PersonTableModel extends AbstractTableModel {
    private final List<Person> personList;
    private final String[] columnNames = {"ID", "Name", "Occupation"};
    public PersonTableModel(List<Person> personList) {
        this.personList = new ArrayList<>(personList); // 使用副本,避免外部修改影响
    }
    @Override
    public int getRowCount() {
        return personList.size();
    }
    @Override
    public int getColumnCount() {
        return columnNames.length;
    }
    @Override
    public String getColumnName(int column) {
        return columnNames[column];
    }
    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        Person p = personList.get(rowIndex);
        switch (columnIndex) {
            case 0: return p.getId();
            case 1: return p.getName();
            case 2: return p.getOccupation();
            default: return null;
        }
    }
    // 2. 提供修改数据的方法
    public void addPerson(Person p) {
        int newRowIndex = personList.size();
        personList.add(p);
        // 通知新行已插入
        fireTableRowsInserted(newRowIndex, newRowIndex);
    }
    public void updatePerson(int row, Person p) {
        personList.set(row, p);
        // 通知行已更新
        fireTableRowsUpdated(row, row);
    }
}
// 简单的 Person 类
class Person {
    private int id;
    private String name;
    private String occupation;
    // 构造函数、Getter 和 Setter
    public Person(int id, String name, String occupation) { /* ... */ }
    public int getId() { return id; }
    public String getName() { return name; }
    public String getOccupation() { return occupation; }
    public void setOccupation(String occupation) { this.occupation = occupation; }
}
// 主程序
public class MVCRefreshExample {
    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> {
            // 准备数据源
            List<Person> data = new ArrayList<>();
            data.add(new Person(1, "John", "Developer"));
            data.add(new Person(2, "Jane", "Designer"));
            // 创建自定义模型和表格
            PersonTableModel model = new PersonTableModel(data);
            JTable table = new JTable(model);
            JFrame frame = new JFrame("JTable MVC Refresh Example");
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.add(new JScrollPane(table), java.awt.BorderLayout.CENTER);
            JButton updateButton = new JButton("Update Jane's Job");
            updateButton.addActionListener(e -> {
                // 3. 通过模型修改数据
                int janeRow = 1;
                Person jane = model.getValueAt(janeRow, 1); // 错误!getValueAt返回Object,不是Person
                // 正确的获取方式:
                Person janeToUpdate = ((PersonTableModel)table.getModel()).personList.get(janeRow);
                janeToUpdate.setOccupation("Senior Designer");
                // 通知模型数据已更新
                ((PersonTableModel)table.getModel()).fireTableRowsUpdated(janeRow, janeRow);
            });
            frame.add(updateButton, java.awt.BorderLayout.SOUTH);
            frame.pack();
            frame.setVisible(true);
        });
    }
}

(注意:上面的 MVC 示例中,从 JTable 获取 Person 对象的演示部分有误,只是为了说明问题,正确的做法是直接持有 PersonTableModel 的引用,如 PersonTableModel model = ...; 然后通过 model.personList.get(...) 来访问数据。)


总结与最佳实践

方法 适用场景 优点 缺点
简单刷新 数据量小,整个表格数据需要替换 简单直接,代码少 效率低,会丢失表格的当前状态(如选中行、排序状态)
高效刷新 数据量大,频繁修改单行/单元格 性能高,保留表格状态 需要手动调用 fireXXX 方法,代码稍复杂
MVC 模式 中大型项目,数据来自外部源 结构清晰,解耦,易于维护和扩展 需要编写自定义 TableModel,初期工作量较大

最终建议

  • 对于学习或小型应用:方法一和方法二足够了。
  • 对于任何严肃的项目强烈推荐使用方法三(MVC 模式),它将数据和视图分离,使你的代码更健壮、更易于维护和扩展,这是处理 JTable 数据的行业标准做法。
分享:
扫描分享到社交APP
上一篇
下一篇