You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

JavaFX中FXML注解属性继承后空指针问题及正确使用方法

问题描述

背景

我正在实现基于JavaFX GUI的CAN总线消息传输功能,消息由用户输入构建。

我有一个和Main.fxml关联的MainViewController类,其中用@FXML注解的in_desiredVelocity文本框属性已经和FXML文件中的fx:id正确绑定。

我定义了抽象类canMessage作为CAN消息的基类,子类PcToVcuMessage实现特定类型的CAN消息。为了让子类能访问MainViewController里的FXML属性,我让canMessage继承MainViewController,PcToVcuMessage再继承canMessage。

应用编译正常,但在GUI的in_desiredVelocity文本框输入内容时触发了NullPointerException。

问题

虽然PcToVcuMessage通过继承拥有了该FXML属性,我该如何在这个类里正确使用它,实现获取用户输入构建CAN消息的目标?


代码实现

MainApplication类

package canbusgui;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Scene;
import javafx.stage.Stage;

import java.io.IOException;

public class MainApplication extends Application {
    public static Stage stage = null;

    @Override
    public void start(Stage stage) throws IOException {
        
        stage.setOnCloseRequest(event -> {
           System.exit(0);
        });

        MainApplication.stage = stage;

        FXMLLoader fxmlLoader = new FXMLLoader(MainApplication.class.getResource("MainView.fxml"));
        Scene scene = new Scene(fxmlLoader.load());

        stage.setTitle("CANbus GUI");
        stage.setScene(scene);
        stage.setResizable(false);
        stage.setMinHeight(768);
        stage.setMinWidth(1366);
        stage.show();
    }

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

MainViewController类

package canbusgui;

import javafx.application.Platform;
import javafx.collections.FXCollections;
import javafx.fxml.FXML;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;


public class MainViewController {
  @FXML
  protected TextField in_desiredVelocity;

  @FXML
  public void initialize (){

        in_desiredVelocity.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                if (event.getCode() == KeyCode.ENTER) {
                    try {
                        sendMessage(new PcToVcuMessage("222"));
                    }
                    catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
        });
  }
  

  public void sendMessage(canMessage message) throws Exception {
        
        message.constructData();
       
        message.validateInputs();
        
        byte[] data = message.getData();

        canBusController.sendCommand(HexFormat.fromHexDigits(message.getId()), data);
  }

}

canMessage类

package canbusgui;

import static java.lang.Integer.parseInt;

public abstract class canMessage extends MainViewController{

    protected String id;
    protected byte[] data;

    public canMessage(String id) {
        this.id = id;
        this.data = new byte[8];
    }

    public abstract void constructData();
    public abstract void validateInputs() throws Exception;
    public byte[] getData() {
        return this.data;
    }
    public String getId() {
        return this.id;
    }
}

class PcToVcuMessage extends canMessage {

    public PcToVcuMessage(String id) {
        super(id);
    }

    @Override
    public void constructData() {
        data[0] = (byte) 0;
        data[1] = (byte) 0;
        data[2] = (byte) 0;
        data[3] = (byte) parseInt(in_desiredVelocity.getText()); // 此处in_desiredVelocity为null,触发空指针异常
        data[4] = (byte) 0;
        data[5] = (byte) 0;
        data[6] = (byte) 0;
        data[7] = (byte) 0;
    }

    public  void validateInputs() throws Exception{}
}

解决方案

核心问题是错误地通过继承让CAN消息类持有控制器的FXML属性:MainViewController的实例由FXMLLoader创建并完成FXML绑定,而你手动new的PcToVcuMessage(继承自MainViewController)是完全独立的实例,它的in_desiredVelocity从未被FXML初始化,自然是空指针。

正确做法是打破不合理的继承关系,让CAN消息类专注于消息构建逻辑,通过参数传递获取用户输入,而非直接依赖UI组件。

具体修改步骤

  1. 移除CAN消息类对MainViewController的继承
    让canMessage回归纯消息基类职责:

    public abstract class canMessage {
        protected String id;
        protected byte[] data;
    
        public canMessage(String id) {
            this.id = id;
            this.data = new byte[8];
        }
    
        public abstract void constructData();
        public abstract void validateInputs() throws Exception;
        public byte[] getData() { return this.data; }
        public String getId() { return this.id; }
    }
    
  2. 修改PcToVcuMessage,通过构造方法传入输入值
    让消息类只负责将数值转为CAN数据格式:

    class PcToVcuMessage extends canMessage {
        private int desiredVelocity;
    
        // 构造时传入需要的输入值
        public PcToVcuMessage(String id, int desiredVelocity) {
            super(id);
            this.desiredVelocity = desiredVelocity;
        }
    
        @Override
        public void constructData() {
            data[0] = (byte) 0;
            data[1] = (byte) 0;
            data[2] = (byte) 0;
            data[3] = (byte) desiredVelocity; // 直接使用传入的数值
            data[4] = (byte) 0;
            data[5] = (byte) 0;
            data[6] = (byte) 0;
            data[7] = (byte) 0;
        }
    
        public void validateInputs() throws Exception {
            // 可在此添加数值范围校验
            if (desiredVelocity < 0 || desiredVelocity > 255) {
                throw new IllegalArgumentException("期望速度超出有效范围(0-255)");
            }
        }
    }
    
  3. 修改MainViewController,创建消息时传入UI输入值
    先从文本框读取并解析数值,再传入消息构造方法:

    public class MainViewController {
        @FXML
        protected TextField in_desiredVelocity;
    
        @FXML
        public void initialize (){
            in_desiredVelocity.addEventFilter(KeyEvent.KEY_PRESSED, event -> {
                if (event.getCode() == KeyCode.ENTER) {
                    try {
                        // 读取并解析输入值
                        int velocity = Integer.parseInt(in_desiredVelocity.getText().trim());
                        // 传入数值创建消息
                        sendMessage(new PcToVcuMessage("222", velocity));
                    } catch (NumberFormatException e) {
                        System.err.println("请输入有效的数字");
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                }
            });
        }
    
        public void sendMessage(canMessage message) throws Exception {
            message.validateInputs();
            message.constructData();
            byte[] data = message.getData();
            canBusController.sendCommand(HexFormat.fromHexDigits(message.getId()), data);
        }
    }
    

扩展方案(应对多属性消息)

如果部分CAN消息需要多个UI输入值:

  • 可为消息类添加多个构造参数,或封装DTO类传递所有参数
  • 始终保持消息类的单一职责:只负责将业务数据转为CAN帧格式,不直接依赖UI组件

这种方式既保留了多态优势(sendMessage可接收任意canMessage子类),又避免了不合理的继承依赖,彻底解决空指针问题。

内容的提问来源于stack exchange,提问作者Giuseppe

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.01 13:17:33