单身中的字段意外为空 - 为什么?

时间:2017-10-27 00:05:14

标签: java javafx nullpointerexception singleton

我在JavaFX程序中有以下单例,旨在使应用程序的不同屏幕之间切换更容易:

public class ScreenManager() {

    private Stage mainStage;

    private static ScreenManager instance;

    private ScreenManager() {
        // TODO
    }

    public static ScreenManager getInstance() {
        if (instance == null ) {
            return new ScreenManager();
        } else {
            return instance;
        }
    }

    public void initialize(Stage mainStage) {
        this.mainStage = mainStage;
    }

    public void switchToScreen(String fxmlPath) {
        Parent newScreenRoot;

        try {
            URL pathToFxml = getClass().getResource(fxmlPath);
            newScreenRoot = fxmlLoader.load(pathToFxml);
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to load FXML", e);
        }

            Scene newScreen = new Scene(newScreenRoot);
            mainStage.setScene(newScreen);
            mainStage.setMaximized(true);
        }

}

在JavaFX的start()方法中,通过引用主阶段来调用Initialize。

然而,当我稍后调用getInstance()然后尝试切换屏幕时,我失败了NullPointerException,因为mainStage为空。看起来该领域在它首次使用和它的后续使用之间变得无效。怎么样?

为什么会这样?

1 个答案:

答案 0 :(得分:2)

您永远不会初始化instance,因此您的getInstance()方法每次都会返回一个新对象(它几乎与单例相反;您已经很难同时使用同一个实例...)。

你需要

public static ScreenManager getInstance() {
    if (instance == null ) {
        instance = new ScreenManager();
    } 
    return instance;
}

只是一些评论:许多程序员不鼓励使用单例模式,因为它有很多问题。您可以考虑使用依赖注入。此外,由于此单例的主要目的似乎是提供对舞台的访问,请注意您可以通过Node.getScene().getWindow()获取对舞台的任何节点的引用(如果您可能需要向下转换结果)需要Stage - 特定功能)。由于控制器始终可以访问UI层次结构中的某个节点,因此您可能根本不需要它。

最后,如果你确定需要/想要使用单例,另一种实现单例模式的方法是使用只有一个值的枚举:

import java.io.IOException;
import java.net.URL;

import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public enum ScreenManager {

    INSTANCE ;

    private Stage mainStage;


    public void initialize(Stage mainStage) {
        this.mainStage = mainStage;
    }

    public void switchToScreen(String fxmlPath) {
        Parent newScreenRoot;

        try {
            URL pathToFxml = getClass().getResource(fxmlPath);
            newScreenRoot = FXMLLoader.load(pathToFxml);
        } catch (IOException e) {
            throw new IllegalArgumentException("Failed to load FXML", e);
        }

            Scene newScreen = new Scene(newScreenRoot);
            mainStage.setScene(newScreen);
            mainStage.setMaximized(true);
        }

}

然后你可以做像

这样的事情
ScreenManager.INSTANCE.initialize(primaryStage);
ScreenManager.INSTANCE.switchToScreen(...);

总的来说,这种方法有一些优点,而不是直接实现它:首先,这是立即线程安全的,而不是我在本文顶部发布的解决方案。