可见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渲染流程,不存在跨线程并发访问,因此序列化可以正常执行。
- 严格遵守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(); } }); } }
- 如果业务场景需要在非UI线程执行序列化,不要直接序列化正被UI组件引用的模型实例,提前拷贝出纯数据模型再做序列化,避免触发Swing内部的监听器联动。
内容的提问来源于stack exchange,提问作者Nur1

