自定义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
相关产品推荐
相关产品推荐

