问题
So recently I was working on an a tool for us here to configure certain applications. It didn't need to be anything really awesome, just a basic tool with some SQL script generation, and creating a couple of XML files. During this I created a series of JTable objects with my own implementation of the AbstractTableModel. After I had built everything, and got to the point where I was testing saving and loading using the AbstractTableModel (just written to disk using the ObjectStreamWriter) serialization failed. It took me almost all day to figure out what was going on. When I would try to serialize them I would get a NotSerializableException on java.lang.reflect.Constructor. I didn't know what this was all about because my table model only contained serializable entities, and all of the listeners I had attached were also serializable, and the parent class is also serializable. After a lot of digging, and a few helpful posts from here I discovered that when you add a TableModelListener to an AbstractTableModel implementation, another listener is added in addition to the one you added, of type javax.swing.event.TableModelListener which isn't serializable (see http://docs.oracle.com/javase/7/docs/api/javax/swing/event/TableModelListener.html for the interface, I don't know the implementation). EDIT The Model doesn't add this non serializable listener, the JTable does. My question is essentially, why would this object add its own nonserializable object internally, thus negating the fact that it does in fact implement Serializable? Is this something I should report as a bug?
FYI the work around I had was to simply remove all of the listeners, serialize, then re-add the listeners. When deserializing I only needed to add the one I created, and the model created the other one on its own again.
Edit Try serializing this Model with the serializer class provided by invoking the setValueAt() method.
import java.io.Serializable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
public class BlankTableModel extends AbstractTableModel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 6063143451207205385L;
public BlankTableModel()
{
this.addTableModelListener(new InnerTableModelListener());
}
@Override
public void setValueAt(Object o, int x, int y)
{
this.fireTableChanged(new TableModelEvent(this, x, y));
}
public int getColumnCount() {
// TODO Auto-generated method stub
return 2;
}
public int getRowCount() {
// TODO Auto-generated method stub
return 2;
}
public Object getValueAt(int arg0, int arg1) {
// TODO Auto-generated method stub
return "Test Data";
}
private void save()
{
Serializer.SerializeObject(this);
}
@Override
public boolean isCellEditable(int rowindex, int colindex)
{
return true;
}
private class InnerTableModelListener implements TableModelListener, Serializable
{
@Override
public void tableChanged(TableModelEvent arg0) {
save();
}
}
}
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
public class Serializer {
public static void SerializeObject(Serializable object)
{
File out = new File("USE A VALID PATH");
if (!out.exists())
{
try {
out.createNewFile();
} catch (IOException e1) {
e1.printStackTrace();
}
}
else
{
out.delete();
try {
out.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try (FileOutputStream fos = new FileOutputStream(out);
ObjectOutputStream oos = new ObjectOutputStream(fos))
{
oos.writeObject(object);
}catch (Exception e)
{
e.printStackTrace();
}
}
}
then try replacing the save method with this
private void save()
{
for (TableModelListener l : this.getTableModelListeners())
{
this.removeTableModelListener(l);
}
Serializer.SerializeObject(this);
this.addTableModelListener(new InnerTableModelListener());
}
here is a simple gui
import java.awt.Dimension;
import javax.swing.JFrame;
import javax.swing.JTable;
public class MainForm extends JFrame {
public static void main(String[] args)
{
MainForm form = new MainForm();
form.show();
}
public MainForm()
{
this.setBounds(100, 100, 600, 600);
BlankTableModel model = new BlankTableModel();
JTable table = new JTable(model);
table.setPreferredSize(new Dimension(500,500));
this.getContentPane().add(table);
}
}
回答1:
JTable
, a TableModelListener
to it's own TableModel
, is Serializable
. Your custom TableModel
is Serializable
. Adding an additional TableModelListener
that is also Serializable
should make no difference, as shown below. Some suggestions:
Verify that the data structure contained in your
TableModel
is itselfSerializable
. To the extent that this represents a bug, it may be possible to serialize just the model's internal data structure. For example,System.out.println(copyObject(data));
Critically examine you choice to use serialization in this context; see also Effective Java: Chapter 11. Serialization.
Addendum: I updated the example to instantiate JTable
, clone the table using serialization, update the copy and display both.
Screen:
Console:
New data
SSCCE:
import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.GridLayout;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.event.TableModelEvent;
import javax.swing.event.TableModelListener;
import javax.swing.table.AbstractTableModel;
/* @see http://stackoverflow.com/a/19300995/230513 */
public class SerializationTest {
public static void main(String[] args) {
EventQueue.invokeLater(new Runnable() {
@Override
public void run() {
JTable table = new JTable(new BlankTableModel());
JTable copy = copyObject(table);
copy.setValueAt("New data", 0, 0);
JFrame f = new JFrame("SerializationTest");
f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
f.setLayout(new GridLayout(0, 1, 5, 5));
f.add(table, BorderLayout.NORTH);
f.add(copy, BorderLayout.SOUTH);
f.pack();
f.setLocationRelativeTo(null);
f.setVisible(true);
}
});
}
private static class BlankTableModel extends AbstractTableModel implements Serializable {
private static final long serialVersionUID = 3141592653589793L;
private String data = "Test data";
public BlankTableModel() {
this.addTableModelListener(new InnerTableModelListener());
}
@Override
public void setValueAt(Object o, int row, int col) {
data = o.toString();
this.fireTableCellUpdated(row, col);
}
@Override
public int getColumnCount() {
return 2;
}
@Override
public int getRowCount() {
return 2;
}
@Override
public Object getValueAt(int row, int col) {
return data;
}
private void save() {
BlankTableModel model = copyObject(this);
System.out.println(model.getValueAt(0, 0));
}
@Override
public boolean isCellEditable(int row, int col) {
return true;
}
private class InnerTableModelListener implements TableModelListener, Serializable {
private static final long serialVersionUID = 2718281828459045L;
@Override
public void tableChanged(TableModelEvent e) {
save();
}
}
}
private static <T extends Serializable> T copyObject(final T source) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(source);
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(baos.toByteArray()));
final T copy = (T) ois.readObject();
return copy;
} catch (Exception e) {
throw new AssertionError("Error copying: " + source);
}
}
}
来源:https://stackoverflow.com/questions/19282817/why-do-jtables-make-tablemodels-non-serializable-when-rendered