JavaFX 应用程序抛出 NullPointerExceptions 但仍然运行

Posted

技术标签:

【中文标题】JavaFX 应用程序抛出 NullPointerExceptions 但仍然运行【英文标题】:JavaFX applications throw NullPointerExceptions but run anyway 【发布时间】:2017-11-24 20:38:13 【问题描述】:

我运行的每个 JavaFX 应用程序都会引发两个 NullPointerException。它们不会阻止甚至影响项目的执行,而且只有在调试模式下运行我的应用程序时才能看到它们。我什至遇到了来自 Oracle 的 HelloWorld 示例和这个最小程序的问题:

public class JavaFXTSample extends Application 

    @Override
    public void start(Stage primaryStage) throws Exception 

        StackPane iAmRoot = new StackPane();

        Scene scene = new Scene(iAmRoot, 300, 250);

        primaryStage.setScene(scene);
        primaryStage.show();
    

    public static void main (String[] args) 
        launch(args);
    

这是第一个错误的堆栈跟踪:

 Thread [main] (Suspended (exception NullPointerException)) 
    SystemProperties.setVersions() line: 81 [local variables unavailable]   
    SystemProperties.lambda$static$28() line: 67    
    30621981.run() line: not available  
    AccessController.doPrivileged(PrivilegedAction<T>) line: not available [native method]  
    SystemProperties.<clinit>() line: 64    
    LauncherImpl.startToolkit() line: 668   
    LauncherImpl.launchApplicationWithArgs(String, String, String[]) line: 337  
    LauncherImpl.launchApplication(String, String, String[]) line: 328  
    NativeMethodAccessorImpl.invoke0(Method, Object, Object[]) line: not available [native method]  
    NativeMethodAccessorImpl.invoke(Object, Object[]) line: not available   
    DelegatingMethodAccessorImpl.invoke(Object, Object[]) line: not available   
    Method.invoke(Object, Object...) line: not available    
    LauncherHelper$FXHelper.main(String...) line: not available 

这是第二个:

Thread [JavaFX Application Thread] (Suspended (exception NullPointerException)) 
    PropertyHelper.lambda$getBooleanProperty$514(String) line: 39   
    7164036.run() line: not available   
    AccessController.doPrivileged(PrivilegedAction<T>) line: not available [native method]  
    PropertyHelper.getBooleanProperty(String) line: 37  
    Parent.<clinit>() line: 87  
    JavaFXTSample.start(Stage) line: 16 
    LauncherImpl.lambda$launchApplication1$162(AtomicBoolean, Application) line: 863    
    2266602.run() line: not available   
    PlatformImpl.lambda$runAndWait$175(Runnable, CountDownLatch) line: 326  
    32251660.run() line: not available  
    PlatformImpl.lambda$null$173(Runnable) line: 295    
    11305869.run() line: not available  
    AccessController.doPrivileged(PrivilegedAction<T>, AccessControlContext) line: not available [native method]    
    PlatformImpl.lambda$runLater$174(Runnable, AccessControlContext) line: 294  
    30052382.run() line: not available  
    InvokeLaterDispatcher$Future.run() line: 95 
    WinApplication._runLoop(Runnable) line: not available [native method]   
    WinApplication.lambda$null$148(int, Runnable) line: 191 
    32126786.run() line: not available  
    Thread.run() line: not available    

更重要的是,如果我删除了iAmRootscene 的任何实例(所以start() 只是读取primaryStage.show();),第二个错误不会发生。为什么会这样?

我之前已经能够找到这个问题 (JavaFX application throws NullPointerException at startup),但似乎没有人解决它,并且在 2 多年前被问到。

如果有帮助,我在 Windows 7 Professional 上运行 Eclipse 4.5.2,我认为我根本没有使用 FXML。

编辑:

对于它的价值,我找不到第二个错误的源代码,但我找到了引发第一个错误的方法的 JavaFX 代码(第 81 行):

58  private static final String versionResourceName =
59       "/com/sun/javafx/runtime/resources/version.properties";

...

78 private static void setVersions() 
79     int size;
80     InputStream is =
81         SystemProperties.class.getResourceAsStream(versionResourceName);
82     try  
83         size = is.available();
84         
85         byte[] b = new byte[size];
86         int n = is.read(b);            
87         String inStr = new String(b, "utf-8");
88         SystemProperties.setFXProperty("javafx.version",
89             getValue(inStr, "release="));
90 
91         SystemProperties.setFXProperty("javafx.runtime.version",
92             getValue(inStr, "full="));
93 
94       catch (Exception ignore) 
95      
96 

【问题讨论】:

现在又回来了。但它仍然作为图像发布,真的很难阅读......“Thread [Main](Suspended......)”很奇怪。你是在调试模式下运行的吗? 我将其重新格式化为代码,并在调试中运行它。这些错误根本不会得到处理,它们不会影响代码,因此查看它们的唯一方法就是在调试模式下运行。 那么如果你正常运行,它们不会出现在控制台中? 他们没有。除非它处于调试模式,否则就像永远不会发生错误一样。我想据我所知,它们只发生在调试模式下,但我认为情况并非如此。将对 launch() 的调用封装在 try-catch 中也不会影响任何事情。 一个方法能带来多少代码异味? 1) 假设InputStream.available() 为总文件大小,2) 忽略read(…) 的返回值,3) 不关闭InputStream,4) 捕获并忽略所有异常。讨论 5),考虑到所有这些其他气味,变量的不必要的广泛范围将毫无意义…… 【参考方案1】:

免责声明

这个问题已经 1 岁了,但我觉得仍然非常相关。这就是我回答这个问题的原因(长篇大论)。

我会尽我所能回答这个问题

TL;DR

JavaFX 有一些古怪的代码行,异常是由这些代码行引起的。所有这些都可以追溯到 JavaFX 中的某行,它错过了检查或使用了未检查的部分。

我运行的每个 JavaFX 应用程序都会引发两个 NullPointerException。它们不会阻止甚至影响项目的执行,而且只有在调试模式下运行我的应用程序时才能看到它们。

第一个 NullPointerException

您显示的第一个异常位于:

SystemProperties.setVersions() 行:81 [局部变量不可用]

您已经发布了相关代码。为了形象化这一点,我会再次发布。问题所在的行是:

InputStream is =
            SystemProperties.class.getResourceAsStream(versionResourceName);
try  
    size = is.available(); // InputStream "is" = null
    ...
 catch (Exception ignore) 

这很容易解释。 getResourceAsStream 方法返回以下内容(来自JavaDoc):

返回:一个 InputStream 对象,如果没有找到同名的资源,则返回 null

这意味着找不到资源(您再次发布过的)"/com/sun/javafx/runtime/resources/version.properties"

然而,这没有处理,只是被忽略了。因此不打印此异常,但仍会抛出此异常。它被调试器捕获,但仅此而已。因此只能由调试器识别。

原因不是很清楚。一种可能性可能是 JDK 不包含该资源。在我对 JDK-10(我目前使用的)的测试中,包 com.sun.javafx.runtime 存在,但子包资源不存在。这并不是真正可重现的,因为 com.sun 包似乎没有记录。 javafx 的基本 api 记录在 here,但 com.sun.javafx 包没有。如果您使用的是复杂的 IDE(例如 IntelliJ),您可以查看它。我的 JDK-8 确实包含它。我在 Ubuntu 18.04.1 上使用 Oracle JDK。

一种可能是,包裹在途中被丢弃了。可能有人认为,通过包引用资源不是很好,把它放在资源路径中,但是由于异常被吞没了,所以从未检测到这个错误。

解决此问题

注意:此潜在修复未经过彻底测试

如果您手动引入资源,此问题可能会得到解决。考虑到您发布的代码(可以在 SystemProperties.setVersions 方法中找到),您必须在 com.sun.javafx.runtime.resources 包中创建一个名为“version.properties”的文件。这个versions.properties应该是这样的:

release=1
full=1

这些是您必须测试的任意值。

第二个 NullPointerException

第二个 NullPointer 植根于行 PropertyHelper.lambda$getBooleanProperty$514(String) line: 39。这个方法看起来像这样:

static boolean getBooleanProperty(final String propName) 
    try 
        boolean answer = AccessController.doPrivileged((java.security.PrivilegedAction<Boolean>) () -> 
                    String propVal = System.getProperty(propName);
                    return "true".equals(propVal.toLowerCase()); // Line 39
                );
        return answer;
     catch (Exception any) 
    
    return false;

由此得出结论,propVal 为空,由 JavaDoc from System.getProperty 支持

返回:系统属性的字符串值,如果没有具有该键的属性,则返回 null。

这意味着,propName 未找到。但是propName 是方法参数。那么,propName 是什么?这又可以在堆栈跟踪中找到。行:

Parent.() 行:87

定义应该找到的属性。内容如下:

private static final boolean warnOnAutoMove = PropertyHelper.getBooleanProperty("javafx.sg.warn");

因此,前面所述的方法尝试查找 SystemProperty "javafx.sg.warn",但不检查它是否存在。这又不被处理,因此仅在调试器中可见。

如果删除“toLowerCase”调用,则不会发生此异常。

修复此异常

您可以通过在以任何方式引用 Parent 类之前引入以下代码来简单地修复此异常:

System.setProperty("javafx.sg.warn", "true");

它必须在任何引用之前完成,因为这个属性是静态的。因此,如果以任何方式在代码中 引用此类,它就会被加载。一种可能性是,向包含此代码的主类添加一个静态块。另一种方法是添加以下to the command line:

-Djavafx.sg.warn="true"

这将消除在引用之前调用代码的需要。当然,您可以将 true 换成其他任何东西。但是这样,属性就存在了,问题行不会抛出 NullPointerException。

为什么只执行Stage.show不会抛出第二个异常?

这很简单。在 JavaFx 中,要显示的任何元素都是 Scene graph 中的 Node。每个节点可能有一个Parent、which is a subclass of Node。几乎所有类都扩展了 Parent,so does the StackPane,但 not the Stage。通过引用 StackPane,您引用了 Parent,它触发了属性的加载。如果不设置 StackPane,则不会引用 Parent 类,这意味着不会触发属性的加载。所以不会抛出异常。

您应该修复异常吗?

由于 javafx 背后的团队似乎忽略了这些异常,因此您也可以。我并不是说这是一种好的做法,或者您应该以这种方式编写代码,但是解决这些问题,这些问题不会阻止您运行此应用程序并且不改变行为可能会花费更多的时间而不是值得的。

进一步的想法

这个答案解释了原始的“为什么”,但我想把两分钱花在真正的问题上。请注意,这只是我的意见!如果你有不同的想法,那完全没问题。不再需要直接回答问题。

这一切的根源(在我看来)是 javafx api 中的许多代码异味。从所有部分中的魔术字符串,到 JavaFX 组件中所有 ParentHelper.ParentAccessor 实现中的 downcasting,经过许多大型类(甚至可能是上帝类),最后以不适当的亲密关系结束。抽象可能会更好,并且(例如)Node 类的大小非常庞大。

对于new release cycle,我寄予厚望,希望他们花时间至少消除其中一些代码异味,但我担心它们只是建立在这些代码异味的基础上,这意味着在某些时候,新的 ( “现代”)GUI-API 必须构建。

终于

祝你有美好的一天!

【讨论】:

以上是关于JavaFX 应用程序抛出 NullPointerExceptions 但仍然运行的主要内容,如果未能解决你的问题,请参考以下文章

nullpointer遍历xml

JavaFX布尔绑定和TableView绑定多个

使用PrimeFaces自动完成获取nullpointer异常

使用 Maven 的 JavaFX 11 抛出异常:“WindowsNativeRunloopThread”

javaFX Alert抛出无法捕获的非法状态异常?

JavaFX使用Gluon SceneBuilder InvocationTargetException