复制JTable指定列到新JTable时触发数组越界异常问题排查
JTable复制指定列时ArrayIndexOutOfBoundsException问题排查与解决
问题背景
在Frame中展示完整JTable,需在对话框中展示该表的指定列精简版本。通过创建新TableModel和TableColumnModel复制原表、跳过不需要的列时,对话框显示触发无限错误循环,起始异常为ArrayIndexOutOfBoundsException: 3 >= 2。精简表仅有2列,但列索引3超出范围,调用getColumnCount()检查列数结果正常。尝试用DefaultTableModel.addColumn()方法可成功复制,但丢失列宽信息,需保留列宽的前提下解决当前错误。
原代码
import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; public class CopyTable extends JFrame { public static final long serialVersionUID = 100L; public CopyTable() { setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocationRelativeTo(null); JTable table= createTable(); JScrollPane scroll= new JScrollPane(table); add(scroll, BorderLayout.CENTER); JButton b= new JButton("Show dialog"); b.addActionListener(e -> createAndShowDialog(table)); add(b, BorderLayout.SOUTH); setSize(table.getColumnModel().getTotalColumnWidth()+11, 240); setVisible(true); } static public void main(String args[]) { EventQueue.invokeLater(CopyTable::new); } // @param columns Indices of columns to be copied into the new table. public JTable copyTable(JTable table, int... columns) { DefaultTableModel tblModel= new DefaultTableModel(0, columns.length); DefaultTableModel oldModel= (DefaultTableModel)table.getModel(); Object[] row= new Object[columns.length]; int iRow= 0; while (iRow<oldModel.getRowCount()) { for (int i=0; i<columns.length; i++) { row[i]= oldModel.getValueAt(iRow, columns[i]); } tblModel.addRow(row); iRow++; } DefaultTableColumnModel colModel= new DefaultTableColumnModel(); DefaultTableColumnModel oldColModel= (DefaultTableColumnModel)table.getColumnModel(); for (int i=0; i<columns.length; i++) { colModel.addColumn(oldColModel.getColumn(columns[i])); /* Creating a new column didn't help. TableColumn tc= new TableColumn(); tc= oldColModel.getColumn(columns[i]); colModel.addColumn(tc); */ } return new JTable(tblModel, colModel); } private void createAndShowDialog(JTable table) { JTable tbl = copyTable(table, 0, 3); // Copy only columns 0 and 3. JOptionPane.showMessageDialog(this, ""+tbl.getModel().getRowCount()+", "+ tbl.getModel().getColumnCount()+", "+ tbl.getColumnModel().getColumnCount(), "Checking row/column count", JOptionPane.INFORMATION_MESSAGE); JDialog dlg= new JDialog(this, "Reduced table", true); dlg.setLocationRelativeTo(this); JScrollPane scroll = new JScrollPane(tbl); dlg.add(scroll, BorderLayout.CENTER); dlg.pack(); dlg.setVisible(true); } private JTable createTable() { String headers[] = {"Fruit", "Colour", "Count", "Price"}; Object data[][] = { {"Apple", "Green", 6, .3}, {"Banana", "Yellow", 3, .4}, {"Cherry", "Red", 10, 1.1} }; DefaultTableModel model = new DefaultTableModel(data, headers) { public Class<?> getColumnClass(int column) { Class<?> returnValue; if (column>=0 && column<getColumnCount() && getValueAt(0,column)!=null) returnValue= getValueAt(0, column).getClass(); else returnValue= Object.class; return returnValue; } }; JTable tbl= new JTable(model); TableColumnModel tcm= tbl.getColumnModel(); int[] width= new int[] {60, 50, 40, 40}; for (int i=0; i<headers.length; i++) { tcm.getColumn(i).setPreferredWidth(width[i]); tcm.getColumn(i).setWidth(width[i]); } return tbl; } }
异常信息
Exception in thread "AWT-EventQueue-0" java.lang.ArrayIndexOutOfBoundsException: 3 >= 2 at java.base/java.util.Vector.elementAt(Vector.java:466) at java.desktop/javax.swing.table.DefaultTableModel.getValueAt(DefaultTableModel.java:661) at java.desktop/javax.swing.JTable.getValueAt(JTable.java:2763) at java.desktop/javax.swing.JTable.prepareRenderer(JTable.java:5780) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2207) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2109) at java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1905) at java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161) at java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:852)
问题原因
直接从原TableColumnModel获取的TableColumn对象保留了原表的modelIndex。例如原表第3列的modelIndex是3,但新创建的TableModel只有2列(索引0和1)。当JTable渲染单元格时,TableColumn会用自身的modelIndex去新TableModel中取值,导致索引越界。
解决方案
复制TableColumn时,需要创建原列的副本,并将副本的modelIndex设置为新表中的列索引(即循环变量i),确保与新TableModel的列索引对应。
修改后的copyTable方法:
public JTable copyTable(JTable table, int... columns) { DefaultTableModel tblModel= new DefaultTableModel(0, columns.length); DefaultTableModel oldModel= (DefaultTableModel)table.getModel(); Object[] row= new Object[columns.length]; int iRow= 0; while (iRow<oldModel.getRowCount()) { for (int i=0; i<columns.length; i++) { row[i]= oldModel.getValueAt(iRow, columns[i]); } tblModel.addRow(row); iRow++; } DefaultTableColumnModel colModel= new DefaultTableColumnModel(); DefaultTableColumnModel oldColModel= (DefaultTableColumnModel)table.getColumnModel(); for (int i=0; i<columns.length; i++) { // 创建原TableColumn的副本,避免引用原对象的modelIndex TableColumn originalCol = oldColModel.getColumn(columns[i]); TableColumn newCol = new TableColumn(); // 复制原列的属性:表头、宽度、渲染器等 newCol.setHeaderValue(originalCol.getHeaderValue()); newCol.setPreferredWidth(originalCol.getPreferredWidth()); newCol.setWidth(originalCol.getWidth()); newCol.setMinWidth(originalCol.getMinWidth()); newCol.setMaxWidth(originalCol.getMaxWidth()); newCol.setCellRenderer(originalCol.getCellRenderer()); newCol.setCellEditor(originalCol.getCellEditor()); // 设置新的modelIndex,对应新TableModel的列索引 newCol.setModelIndex(i); colModel.addColumn(newCol); } return new JTable(tblModel, colModel); }
关键修改点
- 不直接添加原
TableColumn对象,而是创建副本,避免继承原列的modelIndex。 - 复制原列的所有关键属性(表头、宽度、渲染器等),保留原列的显示样式。
- 将新列的
modelIndex设置为当前循环的索引i,确保与新TableModel的列索引匹配。
内容的提问来源于stack exchange,提问作者Jörg
相关产品推荐
相关产品推荐

