2017-08-07 46 views
0

在设置了某个外观并导致java.io.NotSerializableException: com.sun.java.swing.plaf.windows.XPStyle$Skin异常,然后是不同的NullPointerException s后,序列化扩展AbstractTableModel的类。在设置外观时序列化AbstractTableModel失败

我找到了一个解决方案,并回答我自己的问题,以帮助他人节省时间,并从一开始就正确实现序列化。附件是一个最小的例子,它重现了我的机器上的错误(Win 10,Netbeans IDE 8.2,Java JDK 1.8)。下面的答案是主要代码段和更多细节。

回答

2

主类延伸JFrame,设置外观并显示JTable。它有一个成员变量Tip tip,其中Tip延伸AbstractTableModel(见下文)。

public class MainFrame extends javax.swing.JFrame { 

    // a single tip 
    // in my final app, there was a list of tips 
    Tip tip = new Tip(); 

    public MainFrame() { 
     initComponents(); 

     // fill new instance with some dummy data for answers 
     tip.addAnswer("first answer", 1, "first reply"); 
     tip.addAnswer("second answer", 2, "second reply"); 

     // assign the table model 
     jTable.setModel(tip); 
    } 

    // ... more code (see attachment) 

    public static void main(String args[]) { 

     /* Set the look and feel */ 
     try { 
      for (javax.swing.UIManager.LookAndFeelInfo info : javax.swing.UIManager.getInstalledLookAndFeels()) { 
       if ("Windows".equals(info.getName())) { 
        /** 
        * when setting LaF to 'Nimbus' de-/serialization fails on the first save/load: 
        * Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException 
        * at javax.swing.plaf.synth.SynthLookAndFeel.paintRegion(SynthLookAndFeel.java:371) 
        * 
        * when setting LaF to 'Windows' the second save/load fails with: 
        * java.io.NotSerializableException: com.sun.java.swing.plaf.windows.XPStyle$Skin 
        * at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184) 
        * 
        * when setting LaF to 'Metal' save/load works just fine 
        * 
        * not setting LaF at all (uncomment the line below) also works fine 
        */ 
        javax.swing.UIManager.setLookAndFeel(info.getClassName()); 
        break; 
       } 
      } 
     } catch (Exception ex) { 
      Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); 
     } 


     /* Create and display the form */ 
     java.awt.EventQueue.invokeLater(new Runnable() { 
      public void run() { 
       new MainFrame().setVisible(true); 
      } 
     }); 
    } 

Tip持有一些成员变量应该在表(自定义类型TipAnswer)显示,并且所需要的业务逻辑,但没有在表中显示的附加也是成员变量:

public class Tip extends AbstractTableModel { 

    // ... more member variables that don't show up in the table 

    // a list of answers to the tip which show up in the table 
    private ArrayList<TipAnswer> answers = new ArrayList<>(); 

    // adds an answer to the tip 
    public void addAnswer(String answer, int cost, String reply) {  
     answers.add(new TipAnswer(answer, cost, reply)); 
    } 

    // ... more methods that override the methods 
    //  required by AbstractTableModel (see attachment) 
} 

主类按下按钮,成员变量tip被序列化并立即反序列化。表中列出了反序列化的结果。

private void jSaveAndLoadActionPerformed(java.awt.event.ActionEvent evt) { 

    // make temp file 
    Path path; 
    try { 
     path = Files.createTempFile("TestTableModel", ".txt"); 
    } catch (IOException ex) { 
     Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); 
     return; 
    } 

    // write to file (serialize) 
    try (FileChannel channel = FileChannel.open(path, StandardOpenOption.CREATE, 
                 StandardOpenOption.WRITE, 
                 StandardOpenOption.TRUNCATE_EXISTING); 
      ObjectOutputStream oos = new ObjectOutputStream(Channels.newOutputStream(channel)) 
     ) { 

     // write object to file 
     oos.writeObject(tip); 
     System.out.println("Tip table saved as " + path); 

    } catch (Exception ex) { 
     Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); 
     return; 
    } 

    // set an empty table model for demonstration purposes 
    jTable.setModel(new Tip()); 

    // read from file (deserialize) 
    Tip tipFromFile; 
    try (FileChannel channel = FileChannel.open(path, StandardOpenOption.READ); 
      ObjectInputStream ois = new ObjectInputStream(Channels.newInputStream (channel))) { 

     // the instance to return 
     tipFromFile = (Tip)ois.readObject(); 

    } catch (Exception ex) { 
     Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex); 
     return; 
    } 

    // store the tip which has just been read in this instance 
    tip = tipFromFile; 

    // show the tip in the table 
    jTable.setModel(tip); 
} 

在主类的评论指出,保存和加载tip当没有外观和手感设定,或者当选择“金属”工作正常。设置“Nimbus”LaF,序列化失败,出现NullPointerException。设置'Windows'LaF,序列化失败,至少在我的机器上出现java.io.NotSerializableException: com.sun.java.swing.plaf.windows.XPStyle$Skin异常。

Javadoc javax.swing.UIManager.LookAndFeelInfo声称它实现了Serializable,所以我不会期望这个例外。

用文本编辑器检查保存的文件表明,除了tip的成员变量外,还存储了许多其他字段,例如javax.swing.event.TableModelListeners,autoCreateColumnsFromModel等等。起初我怀疑这会导致异常,我只需要覆盖类Tip中的writeObject(java.io.ObjectOutputStream out)writeObject(java.io.ObjectOutputStream out),但其他字段仍保存到磁盘。第二个想法很清楚为什么。有趣的是,我不会猜到额外的字段会被保存,因为我无法找到AbstractTableModel的javadoc中的那些字段的提示,也没有在TableModel中找到提示,但是没有那么重要。

那么什么帮助是实现一个实例化时接收tip一个单独的类TipTableModel extends AbstractTableMode

public TipTableModel(Tip tip) { 
    this.tip = tip; 
} 

所以不是序列化实现AbstractTableModel一个类型的变量,它似乎是可取的从上述数据中分离表模型并仅序列化数据。

在调试时,我还意识到扩展AbstractListModel(List,不是表)的对象除了在类中声明的成员变量外,还保存了许多字段,但序列化这些成员并不会导致任何异常,尽管它将数据和列表模型分开可能也是可取的。

简历:1)总是实现数据和在两个独立的类表的模型。 2)也许某些外观和感觉需要增强(尽管我仍然认为我犯了一个错误,而不是Java的开发者),并且3)为javadoc添加一条评论,以澄清很多非显而易见的字段是序列化的以及。

Netbeans project of this example