Java FlatLaf - 在 UI 可见时在另一个线程上序列化 TableModel 时出现 NullPointerException

Java FlatLaf - NullPointerException when serializing TableModel on another Thread while UI is visible

提问人:Nur1 提问时间:6/14/2022 最后编辑:Andrew ThompsonNur1 更新时间:6/26/2022 访问量:283

问:

当关联的 JTable 对象在 JFrame 中可见时,尝试序列化 TableModel 对象时,会引发 NullPointerException。我创建了一个最小的、可重现的示例,并且还将包括 Stack-Trace。

这是 NullPointerException 原因:

Cannot invoke "javax.swing.CellRendererPane.paintComponent(java.awt.Graphics, java.awt.Component, java.awt.Container, int, int, int, int, boolean)" because "this.rendererPane" is null

我希望有人能帮忙。也许下面的堆栈跟踪有助于更好地理解它。

我正在使用 AdoptOpenJDK14 (14.0.2.12-hotspot)

下面是最小的、可重现的示例:

序列化 .java(程序的入口点)

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.lang.reflect.InvocationTargetException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;
import javax.swing.table.AbstractTableModel;

import com.formdev.flatlaf.FlatDarkLaf;

public class Serialization {

    enum SERIALIZATION_THREAD {
        MAIN,
        THREAD,
        EDT
    }

    public static void main(String[] args) throws Exception {

        // Create GUI with frame and model
        UI ui = new UI();

        // Serialize the table model
        System.out.println("SERIALIZING ON MAIN THREAD");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.MAIN); // DOESN'T WORK

        System.out.println("SERIALIZING ON SEPARATE THREAD");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.THREAD); // DOESN'T WORK

        System.out.println("SERIALIZING ON EDT");
        sleep(1000);
        serialize(ui.model, SERIALIZATION_THREAD.EDT); // WORKS
    }

    public static void serialize(Serializable object, SERIALIZATION_THREAD thread) {
        switch (thread) {
            case MAIN:
                serialize0(object);
                break;
            case THREAD:
                new Thread(() -> serialize0(object)).start();
                break;
            case EDT:
                SwingUtilities.invokeLater(() -> serialize0(object));
                break;
            default:
                break;
        }
    }

    public static void serialize0(Serializable object) {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("some.dat"))) {
            oos.writeObject(object);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void sleep(int ms){
        try {
            Thread.sleep(ms);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class UI {

    JFrame frame;
    MyAbstractModel model;

    public UI() throws InvocationTargetException, InterruptedException {

        SwingUtilities.invokeAndWait(() -> {
            FlatDarkLaf.setup(); // When using FlatLaf, it causes a NullPointer when serializing.

            model = new MyAbstractModel();
            frame = new JFrame();
            frame.add(new JTable(model));
            frame.setSize(300, 300);
            frame.setLocationRelativeTo(null);
            frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
            frame.setVisible(true);

            System.out.println("Done Building GUI");

        });
    }
}

class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel() {
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO
    }
}

这是我得到的 Stacktrace。

线程“AWT-EventQueue-0”java.lang.NullPointerException 中的异常:无法调用“javax.swing.CellRendererPane.paintComponent(java.awt.Graphics, java.awt.Component, java.awt.Container, int, int, int, int, boolean)”,因为“this.rendererPane”为空 在 java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCell(BasicTableUI.java:2191) 在 java.desktop/javax.swing.plaf.basic.BasicTableUI.paintCells(BasicTableUI.java:2092) 在java.desktop/javax.swing.plaf.basic.BasicTableUI.paint(BasicTableUI.java:1888) 在 com.formdev.flatlaf.ui.FlatTableUI.paint(FlatTableUI.java:397) 在 java.desktop/javax.swing.plaf.ComponentUI.update(ComponentUI.java:161) 在 java.desktop/javax.swing.JComponent.paintComponent(JComponent.java:797) 在 java.desktop/javax.swing.JComponent.paint(JComponent.java:1074) 在 java.desktop/javax.swing.JComponent.paintToOffscreen(JComponent.java:5255) 在 java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBufferedImpl(RepaintManager.java:1643) 在 java.desktop/javax.swing.RepaintManager$PaintManager.paintDoubleBuffered(RepaintManager.java:1618) 在java.desktop/javax.swing.RepaintManager$PaintManager.paint(RepaintManager.java:1556) 在java.desktop/javax.swing.RepaintManager.paint(RepaintManager.java:1323) 在 java.desktop/javax.swing.JComponent._paintImmediately(JComponent.java:5203) 在 java.desktop/javax.swing.JComponent.paintImmediately(JComponent.java:5013) 在 java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:865) 在 java.desktop/javax.swing.RepaintManager$4.run(RepaintManager.java:848) 在 java.base/java.security.AccessController.doPrivileged(AccessController.java:391) 在 java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) 在 java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:848) 在 java.desktop/javax.swing.RepaintManager.paintDirtyRegions(RepaintManager.java:823) 在 java.desktop/javax.swing.RepaintManager.prePaintDirtyRegions(RepaintManager.java:772) 在java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1884) 在 java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) 在 java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) 在 java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) 在 java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) 在 java.base/java.security.AccessController.doPrivileged(AccessController.java:391) 在 java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) 在 java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) 在 java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) 在 java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) 在 java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) 在 java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109) 在 java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) 在 java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) 线程“AWT-EventQueue-0”java.lang.NullPointerException中的异常:无法调用“javax.swing.JTable.getColumnModel()” 因为“this.table”为 null 在 java.desktop/javax.swing.plaf.basic.BasicTableUI.getPreferredSize(BasicTableUI.java:1768) 在 java.desktop/javax.swing.JComponent.getPreferredSize(JComponent.java:1680) 在 java.desktop/javax.swing.JTable.setWidthsFromPreferredWidths(JTable.java:3205) 在 java.desktop/javax.swing.JTable.doLayout(JTable.java:3117) 在 java.desktop/java.awt.Container.validateTree(Container.java:1722) 在 java.desktop/java.awt.Container.validateTree(Container.java:1731) 在 java.desktop/java.awt.Container.validateTree(Container.java:1731) 在 java.desktop/java.awt.Container.validateTree(Container.java:1731) 在 java.desktop/java.awt.Container.validate(Container.java:1657) 在 java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:745) 在 java.desktop/javax.swing.RepaintManager$3.run(RepaintManager.java:743) 在 java.base/java.security.AccessController.doPrivileged(AccessController.java:391) 在 java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) 在 java.desktop/javax.swing.RepaintManager.validateInvalidComponents(RepaintManager.java:742) 在 java.desktop/javax.swing.RepaintManager$ProcessingRunnable.run(RepaintManager.java:1883) 在 java.desktop/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:316) 在 java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:770) 在 java.desktop/java.awt.EventQueue$4.run(EventQueue.java:721) 在 java.desktop/java.awt.EventQueue$4.run(EventQueue.java:715) 在 java.base/java.security.AccessController.doPrivileged(AccessController.java:391) 在 java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:85) 在 java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:740) 在 java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203) 在 java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124) 在 java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113) 在 java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101) 在 java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90) 做

Java Swing 模型 NullPointerException JTable

评论

0赞 Hovercraft Full Of Eels 6/14/2022
相关问题
0赞 camickr 6/14/2022
您仍然错过了在 EDT 上创建所有 Swing 组件的要点。这将包括设置 LAF 和创建表。我也不知道为什么你有一个同步列表。Swing 是单线程的。如果正确地对 EDT 上的模型和组件进行了所有更新,则不需要同步列表。不知道它是否会解决任何问题,但代码应遵循 Swing 约定。
1赞 kleopatra 6/14/2022
请将 StackTrace 格式化为代码以使其可读

答:

0赞 g00se 6/14/2022 #1

@camickr是对的。事实上,如果你像这样启动你的 gui,并在 EDT 上创建 gui 组件,你的问题就会消失。记下模式并始终使用它。我不认为问题与序列化有任何关系,但在您恢复我删除的序列化代码后,请随时告诉我其他情况。

import java.io.FileOutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import javax.swing.table.AbstractTableModel;
import java.io.ObjectOutputStream;

import javax.swing.JFrame;
import javax.swing.JTable;
import javax.swing.SwingUtilities;
import javax.swing.WindowConstants;

import com.formdev.flatlaf.FlatDarkLaf;

public class Serialization extends JFrame {

    public void setGui() {
        FlatDarkLaf.setup(); // When using FlatLaf, it causes a NullPointer when serializing.

        MyAbstractModel model = new MyAbstractModel();
        JTable table = new JTable(model);

        getContentPane().add(table);
        setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        try {
            SwingUtilities.invokeAndWait(new Runnable() {
                public void run() {
                    Serialization f = new Serialization();
                    f.setGui();
                    f.setSize(200, 200);
                    f.setVisible(true);
                }
            });
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

class MyAbstractModel extends AbstractTableModel {

    private List<String> list = new ArrayList<>();

    public MyAbstractModel() {
        list = Collections.synchronizedList(new ArrayList<>());
        list.add("A");
        list.add("B");
        list.add("C");
    }

    @Override
    public int getRowCount() {
        return list.size();
    }

    @Override
    public int getColumnCount() {
        return 1;
    }

    @Override
    public Object getValueAt(int rowIndex, int columnIndex) {
        return rowIndex + "-" + columnIndex;
    }

    @Override
    public String getColumnName(int column) {
        return "Column " + column;
    }

    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return String.class;
    }

    @Override
    public boolean isCellEditable(int rowIndex, int columnIndex) {
        return false;
    }

    @Override
    public void setValueAt(Object aValue, int rowIndex, int columnIndex) {
        // TODO
    }
}

根据您的改进,我冒昧地更改为以下内容:main

public static void main(String[] args) throws Exception {
    // Create GUI with frame and model
    UI ui = new UI();
    for (int i = 0; i < args.length; i++) {
        SERIALIZATION_THREAD t = SERIALIZATION_THREAD.valueOf(args[i]);
        // Serialize the table model
        System.out.printf("Serializing on thread %s%n", args[i]);
        serialize(ui.model, t);
        sleep(1000);
    }

}

并发现,与我自己的调查相对应,你唯一可以通过而不会失败的论点是 EDT。似乎对“null”中表的内部引用为空。不知道为什么,但看起来涉及线程。BasicTableUI

评论

0赞 Nur1 6/14/2022
好的,我也玩了一下,似乎在 EDT 中同时拥有表模型和序列化代码可以解决问题。我创建了一个静态序列化方法,该方法在新线程中序列化,但这也导致了错误。尝试从另一个线程序列化模型时,似乎会发生此问题。我最初认为 TableModel 不是 Swing 组件,而更像是一个保存数据的实际模型对象。这就是为什么我没有把所有的代码都放到EDT中。我想知道是否有办法在另一个线程中序列化(例如,在添加关闭钩子时)。谢谢。
0赞 g00se 6/14/2022
BKC公司尽管这是“正确”的做事方式,但在尝试序列化模型时仍然存在一个问题。即使该操作是在专用线程中完成的。可能是 LAF 中的错误(默认 LAF 问题消失) - 目前不太确定。
1赞 camickr 6/14/2022
@Nur1 当尝试从另一个线程序列化模型时,该问题似乎会发生 - 您的最小可重现示例在哪里演示了该问题。从模型中保存数据不应影响组件的绘制。我无法访问您的 LAF,因此我无法进行测试,但也许如果我看到一些代码,我可以发现一个明显的问题,就像我在这里建议的那样。
0赞 g00se 6/14/2022
@Nur1。感谢您的更新。请看我的
0赞 Nur1 6/15/2022
感谢您的额外结论。是的,有点奇怪的行为,我从没想过线程会破坏或清空任何对象。