111

My Application class looks like this:

public class Test extends Application {

    private static Logger logger = LogManager.getRootLogger();

    @Override
    public void start(Stage primaryStage) throws Exception {

        String resourcePath = "/resources/fxml/MainView.fxml";
        URL location = getClass().getResource(resourcePath);
        FXMLLoader fxmlLoader = new FXMLLoader(location);

        Scene scene = new Scene(fxmlLoader.load(), 500, 500);

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

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

The FXMLLoader creates an instance of the corresponding controller (given in the FXML file via fx:controller) by invoking first the default constructor and then the initialize method:

public class MainViewController {

    public MainViewController() {
        System.out.println("first");
    }

    @FXML
    public void initialize() {
        System.out.println("second");
    }
}

The output is:

first
second

So, why does the initialize method exist? What is the difference between using a constructor or the initialize method to initialize the controller required things?

Thanks for your suggestions!

Santosh Gokak
  • 3,393
  • 3
  • 22
  • 24
mrbela
  • 4,477
  • 9
  • 44
  • 79

3 Answers3

164

In a few words: The constructor is called first, then any @FXML annotated fields are populated, then initialize() is called.

This means the constructor does not have access to @FXML fields referring to components defined in the .fxml file, while initialize() does have access to them.

Quoting from the Introduction to FXML:

[...] the controller can define an initialize() method, which will be called once on an implementing controller when the contents of its associated document have been completely loaded [...] This allows the implementing class to perform any necessary post-processing on the content.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Nikos Paraskevopoulos
  • 39,514
  • 12
  • 85
  • 90
  • 2
    I don't understand. The way he does it is over the `FXMLLoader`, right? So I don't see a benefit in waiting for the `initialize()` - method. As soon as the FXML is loaded, the following code has access to the `@FXML` variables. Sure, he does it in the start method and not in the constructor, but would `initialize()` bring any benefit in his case? – codepleb Sep 21 '16 at 14:00
  • 5
    The point is that a constructor can't use instance variables that are injected by the container. It's a chicken-egg problem. So the flow is construction -> injection -> initialization. This is a ubiquitous idiom for IOC frameworks in many languages. The constructor is responsible for allocation memory and ensuring a valid initial state for the instance. The initializer adds behaviour that operates on the post-injected state. This is more flexible than constructor injection. – Dave Feb 03 '21 at 04:14
  • 2
    Example injected state would be references to subviews and controls that are created by the FXML loader. Configuring attributes of those subviews programmatically would be impossible during construction - due to possible instance caching, bidirectional references, order of instantiation restrictions, etc - so you instead are given the initialize() method in which to do that work with the guarantee that all the injected references are non-null and initialized. Cocoa uses a similar approach with viewDidLoad(). – Dave Feb 03 '21 at 04:19
103

The initialize method is called after all @FXML annotated members have been injected. Suppose you have a table view you want to populate with data:

class MyController { 
    @FXML
    TableView<MyModel> tableView; 

    public MyController() {
        tableView.getItems().addAll(getDataFromSource()); // results in NullPointerException, as tableView is null at this point. 
    }

    @FXML
    public void initialize() {
        tableView.getItems().addAll(getDataFromSource()); // Perfectly Ok here, as FXMLLoader already populated all @FXML annotated members. 
    }
}
Itai
  • 6,641
  • 6
  • 27
  • 51
18

In Addition to the above answers, there probably should be noted that there is a legacy way to implement the initialization. There is an interface called Initializable from the fxml library.

import javafx.fxml.Initializable;

class MyController implements Initializable {
    @FXML private TableView<MyModel> tableView;

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        tableView.getItems().addAll(getDataFromSource());
    }
}

Parameters:

location - The location used to resolve relative paths for the root object, or null if the location is not known.
resources - The resources used to localize the root object, or null if the root object was not localized. 

And the note of the docs why the simple way of using @FXML public void initialize() works:

NOTE This interface has been superseded by automatic injection of location and resources properties into the controller. FXMLLoader will now automatically call any suitably annotated no-arg initialize() method defined by the controller. It is recommended that the injection approach be used whenever possible.

gkhaos
  • 694
  • 9
  • 20
  • The no-args initialize() method doesn't even have to be annotated with @FXML – Jan Jan 22 '21 at 12:25
  • 4
    @Jan That's only true if it's `public`. You can make the `initialize()` method (either no-args, or with the same arguments as the one in the interface) `private`, and annotate it `@FXML`, which can enhance encapsulation. – James_D Oct 20 '21 at 12:16