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

可见JFrame中关联JTable的TableModel序列化触发空指针异常

问题现象

当与TableModel关联的JTable组件放置在可见JFrame窗口中展示时,尝试序列化该TableModel对象会抛出NullPointerException。

  • 核心报错信息:
Cannot invoke "javax.swing.JTable.getColumnModel()" 
because "this.table" is null
  • 测试验证结论:不使用JFrame承载JTable时,序列化功能可正常运行
  • 运行环境:AdoptOpenJDK14(14.0.2.12-hotspot)
  • 核心疑问:序列化目标是TableModel本身而非JTable组件,为何序列化过程中会调用JTable相关方法?
最小可复现代码

Serialization.java(程序入口类)

import java.io.FileOutputStream;
import java.io.ObjectOutputStream;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.WindowConstants;

public class Serialization {

    public static void main(String[] args) throws Exception {

        MyAbstractModel model = new MyAbstractModel();
        JTable table = new JTable(model);

        // 创建Frame、放入表格并设置可见后,执行下方序列化代码就会抛出异常
        JFrame frame = new JFrame();
        frame.add(table);
        frame.setSize(300,300);
        frame.setLocationRelativeTo(null);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
        frame.setVisible(true);

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("some.dat"));
        oos.writeObject(model);
        oos.close();

    }
}

MyAbstractModel.java(待序列化的模型类)

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.table.AbstractTableModel;

public class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel(){
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("Test1");
        list.add("Test2");
        list.add("Test3");
        list.add("Test4");
    }

    @Override
    public int getRowCount() {
        return 10;
    }

    @Override
    public int getColumnCount() {
        return 3;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // 待实现
    }
}

Some.java(可序列化测试对象类)

import java.io.Serializable;

public class Some implements Serializable {

    private static final long serialVersionUID = 1L;

    public final int num;
    public final String name;
    transient int ignoreNum;

    public Some(int num, String name, int ignoreNum) {
        this.num = num;
        this.name = name;
        this.ignoreNum = ignoreNum;
    }

    @Override
    public String toString() {
        return "Some{" +
                "num=" + num +
                ", name='" + name + '\'' +
                ", ignoreNum=" + ignoreNum +
                '}';
    }

}
完整异常堆栈

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.JTable.getColumnModel()"
because "this.table" is null
at java.desktop/javax.swing.plaf.basic.BasicTableUI.getPreferredSize(BasicTableUI.java:1768)
at java.desktop/javax.swing.JComponent.getPreferredSize(JComponent.java:1680)
at java.desktop/javax.swing.JTable.setWidthsFromPreferredWidths(JTable.java:3205)
at java.desktop/javax.swing.JTable.doLayout(JTable.java:3117)
at java.desktop/java.awt.Container.validateTree(Container.java:1722)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validate(Container.java:1657)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2762)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4844)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot invoke "javax.swing.JTable.getColumnModel()" because "this.table" is null
at java.desktop/javax.swing.plaf.basic.BasicTableUI.getPreferredSize(BasicTableUI.java:1768)
at java.desktop/javax.swing.JComponent.getPreferredSize(JComponent.java:1680)
at java.desktop/javax.swing.JTable.setWidthsFromPreferredWidths(JTable.java:3205)
at java.desktop/javax.swing.JTable.doLayout(JTable.java:3117)
at java.desktop/java.awt.Container.validateTree(Container.java:1722)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validateTree(Container.java:1731)
at java.desktop/java.awt.Container.validate(Container.java:1657)
at java.desktop/java.awt.Window.dispatchEventImpl(Window.java:2762)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4844)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:772)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:95)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:743)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:391)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:742)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

问题根因

该异常和序列化逻辑本身无直接关联,本质是违反Swing单线程规则导致的跨线程竞态问题:

  • Swing强制要求所有UI组件的创建、渲染、更新、布局操作必须在*AWT事件调度线程(EDT)*上执行,示例代码直接在主线程中创建JFrame、JTable并触发序列化,完全不符合Swing的线程模型要求。
  • AbstractTableModel内部维护了TableModelListener监听器列表,JTable初始化时会把自身注册为模型的监听器,当JFrame设置为可见后,EDT线程会自动触发表格布局、列宽计算流程,该流程会访问绑定的TableModel读取元数据。
  • 主线程执行序列化时,会通过反射遍历模型的所有成员字段,包括上述监听器列表,这个过程和EDT线程正在执行的UI布局流程产生并发竞态,导致BasicTableUI实例持有的table引用在某一时刻被置空,最终抛出空指针。
  • 不使用JFrame承载JTable时不会触发EDT的UI渲染流程,不存在跨线程并发访问,因此序列化可以正常执行。
修复方案
  1. 严格遵守Swing单线程规则,所有Swing组件相关操作全部放到EDT线程执行,涉及UI绑定模型的序列化逻辑也放在EDT中同步执行,彻底消除竞态。修正后的入口代码如下:
import java.io.FileOutputStream;
import java.io.ObjectOutputStream;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

public class Serialization {

    public static void main(String[] args) throws Exception {
        // 所有Swing相关操作提交到EDT线程执行
        SwingUtilities.invokeAndWait(() -> {
            try {
                MyAbstractModel model = new MyAbstractModel();
                JTable table = new JTable(model);

                JFrame frame = new JFrame();
                frame.add(table);
                frame.setSize(300,300);
                frame.setLocationRelativeTo(null);
                frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
                frame.setVisible(true);

                // 序列化和UI操作同线程执行,避免并发冲突
                ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("some.dat"));
                oos.writeObject(model);
                oos.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        });
    }
}
  1. 如果业务场景需要在非UI线程执行序列化,不要直接序列化正被UI组件引用的模型实例,提前拷贝出纯数据模型再做序列化,避免触发Swing内部的监听器联动。

内容的提问来源于stack exchange,提问作者Nur1

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.30 12:21:28