Why do JTables make TableModels non serializable when rendered?

人盡茶涼 提交于 2019-12-22 14:42:04

问题


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 itself Serializable. 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!