2013-06-23 20 views
0

今天我遇到了非常討厭的bug,它涉及在使用標準Java序列化API的GUI反序列化期間觸發擺動偵聽器。簡明地解釋如何複製這種行爲有點困難,所以我在下面發佈了一個小測試用例。這個測試用例不會拋出任何異常,觸發任何編譯器警告,並且當然似乎沒有明確定義的行爲。 這是Oracle的軟件包互操作性文檔中的一個缺陷,灰色區域,還是僅僅是之前嘗試過的東西?在GUI反序列化期間擺動觸發器

爲了說明我的原始用例,我試圖在從磁盤重新加載配置組件時自動更新資產編輯器中的多個選項卡。

package com.ihateswing.ssce; 

import java.awt.BorderLayout; 

public class SSCE implements Serializable { 



    private class Internal extends JPanel { 
     private final JComboBox<String> m_cb = new JComboBox<String>(); 

     Internal(final JComboBox<String> cb) { 
      cb.addActionListener(new AbstractAction() { 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<String>(); 
        for (int i = 0; i < cb.getModel().getSize(); ++i) { 
         dcbm.addElement(cb.getModel().getElementAt(i)); 
        } 
        m_cb.setModel(dcbm); 
       } 
      }); 
      super.add(m_cb); 
     } 
    } 

    private JFrame frame; 
    private JComboBox<String> jce = new JComboBox<String>(); 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      public void run() { 
       try { 
        SSCE window = new SSCE(); 
        window.frame.setVisible(true); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 


    public SSCE() { 
     initialize(); 
    } 

    private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { 
     stream.defaultReadObject(); 
     jce.setSelectedIndex(0); // <-- Seems to have undefined behavior? 
    } 

    private void initialize() { 
     frame = new JFrame(); 
     frame.setBounds(100, 100, 450, 300); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 

     jce.setModel(new DefaultComboBoxModel<String>()); 
     frame.getContentPane().add(new Internal(jce), BorderLayout.NORTH); 
     frame.getContentPane().add(jce, BorderLayout.CENTER); 

     JButton btnAddRandomItem = new JButton("Break"); 
     btnAddRandomItem.addActionListener(new AbstractAction() { 
      private static final long serialVersionUID = 1L; 
      private int i; 

      public void actionPerformed(ActionEvent e) { 
       try { 
        ((DefaultComboBoxModel<String>) jce.getModel()) 
          .addElement(String.valueOf(i++)); 
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
        ObjectOutputStream out = new ObjectOutputStream(baos); 
        out.writeObject(SSCE.this); 
        ByteArrayInputStream in = new ByteArrayInputStream(baos 
          .toByteArray()); 
        ObjectInputStream oin = new ObjectInputStream(in); 
        oin.readObject(); 

        // The line below updates 'Internal' as expected, uncomment 
        // to see... 
        // however with the listener fired from the serialization 
        // method, it breaks? 
        // jce.setSelectedIndex(0); 
       } catch (Throwable t) { 
        t.printStackTrace(); 
       } 
      } 
     }); 
     frame.getContentPane().add(btnAddRandomItem, BorderLayout.SOUTH); 
    } 

} 

樣品與輸出(點擊了「休息」的5倍)

package com.ihateswing.ssce; 

import java.awt.BorderLayout; 

public class SSCE implements Serializable { 



    private class Internal extends JPanel { 
     private final JComboBox<String> m_cb = new JComboBox<String>(); 

     Internal(final JComboBox<String> cb) { 
      cb.addActionListener(new AbstractAction() { 
       @Override 
       public void actionPerformed(ActionEvent e) { 
        DefaultComboBoxModel<String> dcbm = new DefaultComboBoxModel<String>(); 
        for (int i = 0; i < cb.getModel().getSize(); ++i) { 
         dcbm.addElement(cb.getModel().getElementAt(i)); 
        } 
        m_cb.setModel(dcbm); 
        System.out.println("Set Internal.m_cb's model with " + m_cb.getModel().getSize() + " elements"); 
       } 
      }); 
      super.add(m_cb); 
     } 
    } 

    private JFrame frame; 
    private JComboBox<String> jce = new JComboBox<String>(); 

    public static void main(String[] args) { 
     EventQueue.invokeLater(new Runnable() { 
      public void run() { 
       try { 
        SSCE window = new SSCE(); 
        window.frame.setVisible(true); 
       } catch (Exception e) { 
        e.printStackTrace(); 
       } 
      } 
     }); 
    } 


    public SSCE() { 
     initialize(); 
    } 

    private void readObject(java.io.ObjectInputStream stream) throws IOException, ClassNotFoundException { 
     stream.defaultReadObject(); 
     jce.setSelectedIndex(0); // <-- Seems to have undefined behavior? 
    } 

    private void initialize() { 
     frame = new JFrame(); 
     frame.setBounds(100, 100, 450, 300); 
     frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); 
     final Internal internal = new Internal(jce); 
     jce.setModel(new DefaultComboBoxModel<String>()); 
     frame.getContentPane().add(internal, BorderLayout.NORTH); 
     frame.getContentPane().add(jce, BorderLayout.CENTER); 

     JButton btnAddRandomItem = new JButton("Break"); 
     btnAddRandomItem.addActionListener(new AbstractAction() { 
      private static final long serialVersionUID = 1L; 
      private int i; 

      public void actionPerformed(ActionEvent e) { 
       try { 
        ((DefaultComboBoxModel<String>) jce.getModel()) 
          .addElement(String.valueOf(i++)); 
        ByteArrayOutputStream baos = new ByteArrayOutputStream(); 
        ObjectOutputStream out = new ObjectOutputStream(baos); 
        out.writeObject(SSCE.this); 
        ByteArrayInputStream in = new ByteArrayInputStream(baos 
          .toByteArray()); 
        ObjectInputStream oin = new ObjectInputStream(in); 
        oin.readObject(); 
        System.out.println("After returning, Internal.m_cb's model size is " + internal.m_cb.getModel().getSize() + " elements"); 
        // The line below updates 'Internal' as expected, uncomment 
        // to see... 
        // however with the listener fired from the serialization 
        // method, it breaks? 
        // jce.setSelectedIndex(0); 
       } catch (Throwable t) { 
        t.printStackTrace(); 
       } 
      } 
     }); 
     frame.getContentPane().add(btnAddRandomItem, BorderLayout.SOUTH); 
    } 

} 

Set Internal.m_cb's model with 1 elements 
Set Internal.m_cb's model with 1 elements 
After returning, Internal.m_cb's model size is 1 elements 
Set Internal.m_cb's model with 2 elements 
After returning, Internal.m_cb's model size is 1 elements 
Set Internal.m_cb's model with 3 elements 
After returning, Internal.m_cb's model size is 1 elements 
Set Internal.m_cb's model with 4 elements 
After returning, Internal.m_cb's model size is 1 elements 
Set Internal.m_cb's model with 5 elements 
After returning, Internal.m_cb's model size is 1 elements 
+0

它是如何*「破」*? – MadProgrammer

+0

'internal'中的組合框不會使用'SSCE'中的模型進行更新...但是,如果您打印出正在設置的模型,則將在動作偵聽器中設置模型的每個元素。該應用程序是可編譯的,可以運行。測試一下,看看。我將添加示例輸出。帖子已更新。 –

+0

我只能推測Java的反序列化過程會覆蓋對列表模型所做的更改......但對於我來說這些更改在調用「defaultReadObject」之後發生似乎並不直觀。 –

回答

1

我不認爲你正在更新你認爲你正在更新。如果附加到每個println m_cb.hashCode(),則這是相同的輸出。請注意,下面的「Set ...」錯誤消息表明模型每次都在m_cb的不同實例上設置。

Set Internal.m_cb's model with 1 elements: 44160343 
Set Internal.m_cb's model with 1 elements: 1436306574 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 2 elements: 2094948113 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 3 elements: 915510800 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 4 elements: 853795873 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 5 elements: 616759228 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 6 elements: 1385385632 
After returning, Internal.m_cb's model size is 1 elements: 44160343 
Set Internal.m_cb's model with 7 elements: 1283006722 
After returning, Internal.m_cb's model size is 1 elements: 44160343 

m_cb的新實例在每個序列化創建,並要設置它的型號 - 但這些新的組合框不會在你的框架中顯示。

+0

好像我將不得不重新閱讀序列化規範。謝謝。 –

相關問題