OpenJFX 中的建構子與 initialize() 方法
1.概述
OpenJFX(以前稱為 JavaFX)是一個用於創建跨平台軟體應用程式的免費開源平台。它旨在向後相容並現代化地取代Swing ,以彌補其不足。它包含各種模組,用於實現 GUI 控制項、圖形、媒體、Web、FXML 等功能。
在本文中,我們將比較 POJO 的標準建構子方法和 JavaFX 特有的initialize()
方法。首先,我們將掌握 JavaFX 控制器的生命週期,然後將其與建構子進行比較。
最後,我們將研究一些可以在 JavaFX 軟體中採用的陷阱、缺陷和最佳實踐。
2. 建構子 vs. initialize()
在深入比較之前,讓我們先檢查一下在建立一個簡單的控制器類別時如何建立 JavaFX 物件。
2.1. JavaFX 控制器生命週期
當我們宣告一個 FXML 視圖並且可選地在 JavaFX 控制器中實作建構函式或initialize()
方法時,首先呼叫建構函式:
public class MainController implements Initializable {
private final String appName;
@FXML
private Label appNameLabel;
public MainController(String name) {
this.appName = name;
}
@Override
public void initialize(URL location, ResourceBundle res) {
this.appNameLabel.setText(this.appName);
}
}
在程式碼片段中,我們將appNameLabel
視圖綁定到其對應的 FXML 檔案。此特定操作發生在呼叫initialize()
方法之前。更具體地說, FXMLLoader
解析視圖檔案並將相應的視圖注入到控制器欄位中:
一旦欄位注入成功,我們就可以在initialize
方法中安全地存取它們,以便執行註冊事件處理程序和設定樣式等操作。實際上,注入不是在initialize
中進行的,而是在構造函數呼叫之後進行的。因此,這些字段在建構函數中不可用:
在構造過程中,視圖本質上為空。如果我們嘗試在建構函式中存取 FXML 視圖,程式將會拋出NullPointerException
。
因此, initialize()
提供了一種安全的方法,可以在程式執行開始時對 FXML 視圖進行後處理並設定它們。之後,當視圖準備好時,場景就會被渲染。因此,我們可以將 UI 初始化邏輯放在 initialize 方法中。
建構函數 | initialize() |
在建立控制器物件時運行 | 注入 FXML 視圖後自動執行 |
JVM 在物件實例化期間調用 | 由 JavaFX FXML 載入器調用 |
FXML 視圖無法存取 | FXML 視圖可以存取 |
用於設定物件狀態 | 用於UI初始化 |
每個控制器呼叫一次 | 每個控制器呼叫一次 |
可以帶參數 | 僅接受兩個參數: URL 和ResourceBundle |
3.何時使用構造函數
從技術上講,我們可以繞過建構函式並僅使用initialize(),
但使用建構函式仍然有用。
3.1. 依賴注入
我們可以初始化不依賴 FXML 視圖的控制器的內部狀態或上下文。此外,我們還可以使用建構函式進行與 FXML 視圖注入無關的依賴注入,例如服務和資料儲存庫:
public class ProfileController implements Initializable {
private final UserService userService;
private User currentUser;
@FXML
private Label usernameLabel;
public ProfileController(UserService userService) {
this.currentUser = userService.getCurrentUser();
}
@Override
public void initialize(URL location, ResourceBundle resources) {
usernameLabel.setText("Welcome, " + this.currentUser.getName());
}
}
在建構子內部,我們正在為UserService
執行依賴注入。
3.2. 內部狀態
此外,我們也可以使用建構子來記錄日誌和設定指標元件。簡而言之,我們可以使用建構函數進行底層設置,並使用 setter 來設定上下文資料:
public class MainController implements Initializable {
private final Logger logger;
private final MetricsCollector metrics;
@FXML
private Label statusLabel;
public MainController() {
this.logger = Logger.getLogger(DashboardController.class.getName());
this.metrics = new MetricsCollector("dashboard-controller");
logger.info("DashboardController created");
metrics.incrementCounter("controller.instances");
}
@Override
public void initialize(URL location, ResourceBundle resources) {
statusLabel.setText("App is ready!");
logger.info("UI initialized successfully");
}
}
4. 陷阱
使用initialize()
時我們應該記住一些陷阱。
4.1. Initializable 和@FXML
衝突
我們可以透過兩種方式實作initialize()
。第一種是實作Initializable
介面。第二種是使用@FXML
註解來註解initialize()
:
public class MainController implements Initializable {
@FXML
public void initializable() {}
// Throws an error
@override
public void initialize(URL location, ResourceBundle res) {}
}
兩者的工作方式相同,但一個類別中只能存在一個。因此,如果註解的變體存在,則重寫initialize()
方法將引發錯誤。
4.2. 處理 FXML 異常
如果某個 FXML 視圖載入失敗,則不會呼叫initialize
方法。因此,我們應該妥善處理異常,或提供一些後備方案以防萬一:
try {
FXMLLoader loader = new FXMLLoader(getClass().getResource("app-label.fxml"));
Parent root = loader.load();
stage.setScene(new Scene(root));
} catch (IOException e) {
// Log and provide fallback
System.err.println("View failed to load: " + e.getMessage());
stage.setScene(new Scene(new Label("UI failed to load")));
}
4.3. 避免在initialize()
中使用過多的邏輯
initialize()
方法在 JavaFX 執行緒中運行。因此,如果我們在initialize(),
它將阻塞 UI 執行緒。這將導致 UI 卡住或被作業系統標記為「無回應」。
最佳實踐是將initialize()
限制在 UI 上,並將繁重的工作轉移到Task
或服務上。
5. 結論
在本文中,我們了解了建構子與 JavaFX 中的initialize()
的差異。我們也透過一個簡單的範例了解了 JavaFX 控制器生命週期的工作原理以及 JavaFX 上下文如何注入 FXML 欄位。
最後,我們探討了何時應該優先使用建構函式而initialize()
以及使用initialize()
時需要注意的一些陷阱。
與往常一樣,我們的程式碼範例可在 GitHub 上找到。