0

今日、標準の Java シリアライゼーション API を使用した GUI デシリアライゼーション中に、swing リスナーを起動するという非常に厄介なバグに遭遇しました。この動作を再現する方法を簡潔に説明するのは少し難しいので、以下に小さなテスト ケースを投稿しました。このテスト ケースは、例外をスローせず、コンパイラの警告をトリガーしません。また、明確に定義された動作をしていないようです。 これはバグですか、Oracle のパッケージ相互運用ドキュメントの灰色の領域ですか、それとも以前に試みたものですか?

元のユースケースを明らかにするために、ディスクから構成コンポーネントをリロードするときにアセットエディターのいくつかのタブを自動的に更新しようとしていました。

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);
    }

}

出力付きサンプル (「Break」を 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
4

1 に答える 1

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 の新しいインスタンスが作成され、そのモデルを設定していますが、これらの新しいコンボ ボックスはフレームに表示されません。

于 2013-06-24T03:21:49.513 に答える