You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义JSlider UI触发间歇性NullPointerException问题求助

解决JSlider自定义UI中focusInsets导致的间歇性NullPointerException

问题背景

我实现了一个继承自MetalSliderUI的CustomSliderUI类,用于定制所有JSlider的UI样式,但运行程序时会间歇性抛出如下NullPointerException:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException: Cannot read field "left" because "this.focusInsets" is null
at java.desktop/javax.swing.plaf.basic.BasicSliderUI.calculateContentRect(BasicSliderUI.java:729)
at java.desktop/javax.swing.plaf.basic.BasicSliderUI.calculateGeometry(BasicSliderUI.java:698)
at java.desktop/javax.swing.plaf.basic.BasicSliderUI$Handler.componentResized(BasicSliderUI.java:1857)
at java.desktop/java.awt.AWTEventMulticaster.componentResized(AWTEventMulticaster.java:167)
at java.desktop/java.awt.Component.processComponentEvent(Component.java:6447)
at java.desktop/java.awt.Component.processEvent(Component.java:6401)
at java.desktop/java.awt.Container.processEvent(Container.java:2266)
at java.desktop/java.awt.Component.dispatchEventImpl(Component.java:4996)
at java.desktop/java.awt.Container.dispatchEventImpl(Container.java:2324)
at java.desktop/java.awt.Component.dispatchEvent(Component.java:4828)
at java.desktop/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:775)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:720)
at java.desktop/java.awt.EventQueue$4.run(EventQueue.java:714)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:98)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:747)
at java.desktop/java.awt.EventQueue$5.run(EventQueue.java:745)
at java.base/java.security.AccessController.doPrivileged(AccessController.java:400)
at java.base/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:87)
at java.desktop/java.awt.EventQueue.dispatchEvent(EventQueue.java:744)
at java.desktop/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
at java.desktop/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
at java.desktop/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.desktop/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)

对应的自定义UI代码:

static class CustomSliderUI extends MetalSliderUI {
    private final int customTrackWidth;

    public CustomSliderUI(int customTrackWidth) {
        super();
        this.customTrackWidth = customTrackWidth;
        //this.focusInsets = new Insets(0, 0, 0, 0);
        //this.insetCache = new Insets(0, 0, 0, 0);
    }

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        if (this.focusInsets == null) {
            this.focusInsets = new Insets(0, 0, 0, 0);
        }
    }

    @Override
    protected void calculateTrackRect() {
        super.calculateTrackRect();
        trackRect.width = customTrackWidth;
        trackRect.x = (slider.getWidth() - customTrackWidth) / 2;
    }

    @Override
    protected Dimension getThumbSize() {
        return new Dimension(30, 30);
    }

    @Override
    public void paintThumb(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2.setColor(new Color(0xF3D39B));
        int thumbWidth = 10;
        int thumbHeight = 30;
        int x = thumbRect.x + (thumbRect.width - thumbWidth) / 2;
        int y = 0;
        g2.fillRect(x, y, thumbWidth, thumbHeight);
    }

    @Override
    public void paintTrack(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int trackHeight = 30;
        trackRect.width = customTrackWidth;

        // Fülle den ausgefüllten Bereich
        int filledWidth = thumbRect.x - trackRect.x + (thumbRect.width / 2);
        g2.setColor(new Color(0xBB7E3B));
        g2.fillRect(trackRect.x, 0, filledWidth, trackHeight);

        // Fülle den nicht ausgefüllten Bereich
        g2.setColor(new Color(0x773A16));
        g2.fillRect(trackRect.x + filledWidth, 0, trackRect.width - filledWidth, trackHeight);
    }
}

问题分析

focusInsets是BasicSliderUI中的成员变量,在执行super.installUI(c)过程中,可能会触发组件resize事件(比如组件布局调整),此时calculateGeometry方法会被调用,而此时focusInsets还未被初始化,导致空指针异常。原来在installUI中设置focusInsets的时机太晚,无法覆盖super.installUI内部触发的UI计算逻辑。

解决方案

直接在构造方法中初始化focusInsets和insetCache,确保对象创建后这两个变量立即非空,从根源上避免间歇性NPE。

修改后的代码:

static class CustomSliderUI extends MetalSliderUI {
    private final int customTrackWidth;

    public CustomSliderUI(int customTrackWidth) {
        super();
        this.customTrackWidth = customTrackWidth;
        // 提前初始化focusInsets和insetCache,避免异步事件触发时空指针
        this.focusInsets = new Insets(0, 0, 0, 0);
        this.insetCache = new Insets(0, 0, 0, 0);
    }

    @Override
    public void installUI(JComponent c) {
        super.installUI(c);
        // 保留空检查作为兜底逻辑
        if (this.focusInsets == null) {
            this.focusInsets = new Insets(0, 0, 0, 0);
        }
    }

    // 其余方法保持不变
    @Override
    protected void calculateTrackRect() {
        super.calculateTrackRect();
        trackRect.width = customTrackWidth;
        trackRect.x = (slider.getWidth() - customTrackWidth) / 2;
    }

    @Override
    protected Dimension getThumbSize() {
        return new Dimension(30, 30);
    }

    @Override
    public void paintThumb(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        g2.setColor(new Color(0xF3D39B));
        int thumbWidth = 10;
        int thumbHeight = 30;
        int x = thumbRect.x + (thumbRect.width - thumbWidth) / 2;
        int y = 0;
        g2.fillRect(x, y, thumbWidth, thumbHeight);
    }

    @Override
    public void paintTrack(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        g2.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);

        int trackHeight = 30;
        trackRect.width = customTrackWidth;

        // Fülle den ausgefüllten Bereich
        int filledWidth = thumbRect.x - trackRect.x + (thumbRect.width / 2);
        g2.setColor(new Color(0xBB7E3B));
        g2.fillRect(trackRect.x, 0, filledWidth, trackHeight);

        // Fülle den nicht ausgefüllten Bereich
        g2.setColor(new Color(0x773A16));
        g2.fillRect(trackRect.x + filledWidth, 0, trackRect.width - filledWidth, trackHeight);
    }
}

说明

构造方法是对象创建时的第一个执行环节,在这里初始化focusInsets和insetCache,能确保任何后续触发的UI计算事件(比如组件resize)执行时,这两个变量都不会为null,彻底解决间歇性空指针问题。

内容的提问来源于stack exchange,提问作者cmdb

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.15 19:29:53