JavaFX JFXListView刷新异常优化:IndexOutOfBoundsException及性能问题
问题分析与优化方案
先梳理下你遇到的核心问题:
- 刷新操作不遵循JFXSlider设置的间隔,直接立即执行
- 多次刷新后抛出
IndexOutOfBoundsException,且应用逐渐阻塞
从报错栈和你的代码来看,问题根源主要集中在主线程阻塞、重复RMI调用导致的数据不一致、代码冗余臃肿这几点,下面一步步给你拆解优化:
1. 先解决索引越界问题
你在循环里多次调用R.ListeService(),每次调用都会发起一次远程请求,这不仅效率极低,还会因为远程服务返回的列表可能在两次调用间发生变化——比如第一次调用拿到的size是8,第二次调用时列表已经为空,这时get(i)就会直接抛出索引越界异常。
优化方式:只调用一次RMI获取列表并缓存到本地,再遍历本地列表:
@FXML public void getServices(ActionEvent e) { try { // 仅调用一次RMI获取服务列表,缓存到本地变量 List<String> serviceList = R.ListeService(); li_se.getItems().clear(); // 用增强for循环,避免手动索引操作 for (String serviceName : serviceList) { JFXButton b = new JFXButton(serviceName); b.setStyle("-fx-rippler-fill:black;-fx-text-fill:black;-fx-font-size:16px;-fx-background-color:orange;-fx-background-radius:5px;"); // 按钮事件逻辑后面再优化,先保留结构 b.setOnAction(event -> { // ... 原事件代码 }); li_se.getItems().add(b); } } catch (Exception ex) { ex.printStackTrace(); // 可以在主线程弹出提示告知用户刷新失败 } }
2. 把耗时操作移出JavaFX主线程
JavaFX的UI操作必须在主线程执行,但RMI远程调用、大量UI组件创建都是耗时操作,直接放在主线程会导致:
- 主线程阻塞,Timeline的刷新间隔逻辑失效(表现为刷新立即执行)
- 多次调用后线程拥堵,应用卡顿甚至无响应
优化方式:用后台线程执行耗时操作,仅在需要更新UI时用Platform.runLater()切回主线程:
@FXML public void getServices(ActionEvent e) { // 启动后台线程执行耗时操作 new Thread(() -> { try { List<String> serviceList = R.ListeService(); List<JFXButton> buttons = new ArrayList<>(); for (String serviceName : serviceList) { JFXButton b = new JFXButton(serviceName); b.setStyle("-fx-rippler-fill:black;-fx-text-fill:black;-fx-font-size:16px;-fx-background-color:orange;-fx-background-radius:5px;"); // 按钮事件里的RMI调用同样要放后台线程 b.setOnAction(event -> { new Thread(() -> { try { // 远程获取参数列表 ObservableList<String> paramList = FXCollections.observableArrayList(R.paramService(serviceName)); // 切回主线程创建弹窗UI Platform.runLater(() -> showServiceParamDialog(serviceName, paramList)); } catch (Exception ex) { ex.printStackTrace(); Platform.runLater(() -> showErrorDialog("获取服务参数失败")); } }).start(); }); buttons.add(b); } // 切回主线程更新ListView Platform.runLater(() -> { li_se.getItems().clear(); li_se.getItems().addAll(buttons); }); } catch (Exception ex) { ex.printStackTrace(); Platform.runLater(() -> showErrorDialog("刷新服务列表失败")); } }).start(); }
3. 修复刷新间隔不生效的问题
确保你的Timeline是动态绑定JFXSlider值的,比如初始化时:
// 创建刷新Timeline Timeline refreshTimeline = new Timeline(new KeyFrame(Duration.seconds(1), event -> getServices(null))); refreshTimeline.setCycleCount(Animation.INDEFINITE); // 绑定滑块值到刷新间隔 refresh.valueProperty().addListener((obs, oldVal, newVal) -> { // 假设滑块范围是1-10秒,可根据实际调整 refreshTimeline.stop(); refreshTimeline.getKeyFrames().set(0, new KeyFrame(Duration.seconds(newVal.doubleValue()), event -> getServices(null))); refreshTimeline.play(); }); // 启动Timeline refreshTimeline.play();
这样滑块调整时,刷新间隔会实时更新,不会出现跳过间隔立即执行的情况。
4. 简化臃肿的弹窗创建逻辑
把按钮事件里的弹窗代码抽成独立方法,提升代码可读性和可维护性,同时修正你代码里的一个小bug(字符串参数类型的组件添加错误):
// 弹窗创建方法 private void showServiceParamDialog(String serviceName, ObservableList<String> paramList) { VBox vBox = new VBox(10); JFXListView li_par = new JFXListView(); JFXTextField[] fields = new JFXTextField[22]; TextFieldEntier[] fEntiers = new TextFieldEntier[22]; TextFieldReel[] fRéels = new TextFieldReel[22]; ChoiceBox<String> choiceBox = new ChoiceBox<>(); choiceBox.getStylesheets().add(getClass().getResource("/choise-box.css").toExternalForm()); choiceBox.getItems().addAll("true", "false"); int k = 0; for (String st : paramList) { if (st.equals("double")) { fRéels[k] = new TextFieldReel(); fRéels[k].setPadding(new Insets(11)); Label lb = new Label("Argument ( Réel ) : "); lb.setPadding(new Insets(7)); lb.setStyle("-fx-min-Width:178;-fx-text-fill:white;-fx-background-color:orange;-fx-background-radius:5px;"); HBox hb = new HBox(10); hb.getChildren().addAll(lb, fRéels[k]); li_par.getItems().add(hb); } else if (st.equals("java.lang.String")) { fields[k] = new JFXTextField(); fields[k].setMinHeight(35); fields[k].setMinWidth(157); Label lb = new Label("Argument ( Texte ) : "); lb.setMinWidth(178); lb.setPadding(new Insets(7)); lb.setStyle("-fx-text-fill:white;-fx-background-color:orange;-fx-background-radius:5px;"); HBox hb = new HBox(10); hb.getChildren().addAll(lb, fields[k]); // 修正原代码中错误添加fRéels[k]的问题 li_par.getItems().add(hb); } else if (st.equals("int")) { fEntiers[k] = new TextFieldEntier(); fEntiers[k].setPadding(new Insets(11)); Label lb = new Label("Argument ( Entier ) : "); lb.setPadding(new Insets(7)); lb.setStyle("-fx-min-Width:178;-fx-text-fill:white;-fx-background-color:orange;-fx-background-radius:5px;"); HBox hb = new HBox(10); hb.getChildren().addAll(lb, fEntiers[k]); li_par.getItems().add(hb); } k++; } JFXButton folder = new JFXButton("Ouvrir le répertoire"); folder.setDisable(true); JFXDialogLayout content = new JFXDialogLayout(); content.setHeading(new Text(serviceName)); content.setBody(vBox); JFXDialog dialog = new JFXDialog(st_dl, content, JFXDialog.DialogTransition.CENTER); dialog.show(); } // 错误提示弹窗方法 private void showErrorDialog(String message) { JFXDialogLayout content = new JFXDialogLayout(); content.setBody(new Text(message)); JFXDialog dialog = new JFXDialog(st_dl, content, JFXDialog.DialogTransition.CENTER); dialog.show(); }
额外优化建议
- 给RMI调用添加超时时间,避免远程服务无响应时后台线程一直挂起
- 刷新时给ListView添加加载状态(比如Spinner),提升用户体验
- 考虑用
CellFactory复用ListView的单元格,避免每次刷新创建大量新按钮 - 对RMI返回的列表做非空判断,避免空指针异常
内容的提问来源于stack exchange,提问作者Sami
相关产品推荐
相关产品推荐

