2017-08-07 10 views
0

特定のルックアンドフィールを設定した後にAbstractTableModelに拡張されたクラスをシリアライズすると、java.io.NotSerializableException: com.sun.java.swing.plaf.windows.XPStyle$Skin例外の後に別のNullPointerExceptionが発生します。ルックアンドフィールの設定時にAbstractTableModelのシリアライズが失敗する

私は解決策を見つけ、自分の質問に答えて他の人が時間を節約し、最初から正しくシリアル化を実装できるようにしています。添付は、私のマシン(Win 10、Netbeans IDE 8.2、Java JDK 1.8)でエラーを再現する最小限の例です。下の答えでは、メインコードセグメントと詳細があります。

答えて

2

メインクラスはJFrame、ルックアンドフィールはJTableです。メンバー変数Tip tipを持っています。ここでTipAbstractTableModelです(以下を参照)。

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がシリアル化され、すぐに再度デシリアライズされます。デシリアライズの結果を表に示します。何のルック&フィールが設定されていないとき、または「メタル」が選択されたときに保存し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); 
} 

は、正常に動作します。 'Nimbus' LaFを設定すると、シリアル化は失敗し、NullPointerExceptionになります。 'Windows' LaFを設定すると、シリアル化は少なくとも私のマシンではjava.io.NotSerializableException: com.sun.java.swing.plaf.windows.XPStyle$Skinの例外で失敗します。

javax.swing.UIManager.LookAndFeelInfoのJavadocは、それがSerializableを実装していると主張しているので、この例外は予想されませんでした。

保存されたファイルをテキストエディタで調べると、tipというメンバ変数以外にも、javax.swing.event.TableModelListeners,autoCreateColumnsFromModelなど多くのフィールドが保存されていることがわかります。最初に私はこれが例外を引き起こすと思ったので、クラスTipwriteObject(java.io.ObjectOutputStream out)writeObject(java.io.ObjectOutputStream out)をオーバーライドするだけですが、追加のフィールドは引き続きディスクに保存されます。 2番目の考えでは、それはなぜか分かりません。興味深いことに、私はAbstractTableModelTableModelのjavadocのフィールドにヒントが見つからないため、追加のフィールドが保存されているとは思っていませんでした。

ので、インスタンス化時tipを受けて別のクラスTipTableModel extends AbstractTableModeを実装することでした助けた何:

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

だからではなく、AbstractTableModelを実装型の変数をシリアル化するの、からデータを分離することをお勧めしそうですテーブルモデルを作成し、データのみをシリアル化します。

デバッグ時に、AbstractListModel(Listではなく表)を拡張するオブジェクトも、クラスで宣言されているメンバー変数に加​​えて多くのフィールドを保存することに気付きましたが、これらのメンバーをシリアライズしても例外は発生しませんでしたおそらくデータとリストモデルを分離することをお勧めします。

再開:1)データとテーブルモデルを常に2つの別々のクラスに実装します。 2)特定のルック・アンド・フィールを強化する必要があるかもしれないが(私はまだJavaの開発者ではないと誤解しているが)、3)javadocにAbstractTableModelというコメントを追加して、同じように。

Netbeans project of this example

関連する問題