maven 更新搜索字段时线程“JavaFX应用程序线程”java.lang.ArrayIndexOutOfBoundsException中出现JavaFX异常

j5fpnvbx  于 2022-11-22  发布在  Maven
关注(0)|答案(1)|浏览(177)

我正在为Uni的一个项目编写一个小程序,它基本上是一个库程序,用于管理书籍和读/写JSON文件,而不是使用数据库,因为它是我的第一个适当的Java应用程序,所以会更简单。
我正在使用一个TextField来过滤一个ListView,其中包含所有书籍的标题,它工作正常,它在列表中显示正确的书籍,并在选择该书籍时在屏幕上更新相应的信息,问题是,即使程序按预期工作,每次我更新搜索字段时都会抛出错误,我得到一个ArrayIndexOutOfBoundsException

Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: Index -1 out of bounds for length 7
    at javafx.base@19-ea/javafx.collections.transformation.FilteredList.get(FilteredList.java:172)
    at com.libraryproject.javalibrary/com.libraryproject.javalibrary.MainViewController.populateDetails(MainViewController.java:200)
    at com.libraryproject.javalibrary/com.libraryproject.javalibrary.MainViewController.lambda$initialize$3(MainViewController.java:127)
    at javafx.graphics@19-ea/com.sun.javafx.application.PlatformImpl.lambda$runLater$10(PlatformImpl.java:457)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:399)
    at javafx.graphics@19-ea/com.sun.javafx.application.PlatformImpl.lambda$runLater$11(PlatformImpl.java:456)
    at javafx.graphics@19-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run$$$capture(InvokeLaterDispatcher.java:96)
    at javafx.graphics@19-ea/com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java)
    at javafx.graphics@19-ea/com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at javafx.graphics@19-ea/com.sun.glass.ui.win.WinApplication.lambda$runLoop$3(WinApplication.java:184)

在Google上搜索了一番之后,有人建议,当从用户输入更新GUI时,应该在应用程序线程中进行,老实说,我并不完全确定这意味着什么,但无论如何,我遵循了建议,并将随后将更新UI变量的函数 Package 在Platform.runLater(() -> {}中,但问题仍然存在,它是上面的堆栈,现在我完全不知道问题出在哪里,所以,在堆栈发布之后,让我们看看下面显示的部分代码:
我使用FilteredList来通过search过滤listrView,下面是管理它的代码和大多数initialize方法:

private FilteredList<Book> filteredBooks;

...
...

 // inside the initialize method 
@Override
    public void initialize(URL arg0, ResourceBundle arg1) {
    // Populate the variable we use throughout the program with the data from the JSON file
    filteredBooks = new FilteredList<Book>(handleJSON.getBooks());
    // Then update the list view for the first time
    populateView(filteredBooks);

...
...
// section of code responsible to check for search changes, when found, fires populateView once more, this time with the variable updated.
searchField.textProperty().addListener((obs, oldText, newText) -> {

            filteredBooks.setPredicate(book -> {
                if(newText == null || newText.isEmpty() || newText.isBlank()) {
                    return true;
                }
                String lowerCaseCompare = newText.toLowerCase();
                if(book.getTitle().toLowerCase().contains(lowerCaseCompare)) {
                    return true;
                }

                return false;
            });

            Platform.runLater(() -> populateView(filteredBooks));

        }); // Listener
...
...
...

// This one handles the selection of an item in the list, when selected, the fields on the other side of the windows will get populated with the respective data from the book based on the id from the list, since they essentialy share the same FilteredList

listView.getSelectionModel().selectedItemProperty().addListener((obs, oldSel, newSel) -> {
            Platform.runLater(() -> {
                populateDetails(listView.getSelectionModel().selectedIndexProperty().getValue(), filteredBooks);
                editButton.setDisable(false);
            });

正如您所看到的,我用Platform.runLater Package 了将更新窗口中的ListView和字段的所有函数,但似乎没有帮助。
现在来看看populateView函数,该函数在程序第一次打开以及每次搜索字段发生变化时都会触发:

public void populateView(FilteredList<Book> booksList) {
           // clears the listview to avoid old elements stacking in the list.
            listView.getSelectionModel().clearSelection();
            listView.getItems().clear();

        ObservableList<String> rawTitles = FXCollections.observableArrayList();
        for(Book book: booksList) {
            rawTitles.add(book.getTitle());
        }

        listView.setItems(rawTitles);

    } // populateView()

最后但同样重要的是,populateDetails函数根据列表选择填充有关图书的字段:

public void populateDetails(Integer selectedBookID, FilteredList<Book> books) {

        Book currentBook = books.get(selectedBookID);

        titleValue.setText(currentBook.getTitle());
        authorValue.setText(currentBook.getAuthor());
        languageValue.setText(currentBook.getLanguage());
        genreValue.setText(currentBook.getGenre());
        pagesValue.setText(currentBook.getPages().toString());
        yearValue.setText(currentBook.getRelease().toString());

        if (currentBook.getAvailable()) {
            availableRadio.setSelected(true);
        } else {
            unavailableRadio.setSelected(true);
        }
    } // populateDetails

这基本上是我试图在不同的地方使用runlater只是为了确保,我仍然得到相同的堆栈,任何想法是什么可能导致这一点?

yshpjwxd

yshpjwxd1#

堆栈跟踪会告诉您问题的确切原因。当您在populateDetails(...)方法中的MainViewController.java的第200行使用值-1对x1m2 n1 a调用get(..)时,会出现ArrayIndexOutOfBoundsException。查看您的代码,此行一定是

Book currentBook = books.get(selectedBookID);

因此selectedBookID一定是罪魁祸首,其值为-1
selectedBookID是一个传递给该方法的参数,您可以在initialize()方法的lambda表达式中从MainController.java的第127行调用该方法。(同样,此信息位于堆栈跟踪中。)您传递的值为

listView.getSelectionModel().selectedIndexProperty().getValue()

文档会明确告诉您发生这种情况的时间:
选定的索引为-1(表示没有选择)或在基础数据模型大小范围内的整数值。
因此,您的填充细节需要处理未选择任何内容的情况(可能是通过清除文本字段)。我认为监听selectedItemProperty()而不是selectedIndexProperty()会更干净,因为它会直接给您选择的Book(如果未选择任何内容,则为null),并且您不必从列表中检索Book

public void populateDetails(Book currentBook) {

    if (currentBook == null) {
        titleValue.setText("");
        authorValue.setText("");
        languageValue.setText("");
        genreValue.setText("");
        pagesValue.setText("");
        yearValue.setText("");
        availableRadio.setSelected(false);
        unavailableRadio.setSelected(false);
    } else {
        titleValue.setText(currentBook.getTitle());
        authorValue.setText(currentBook.getAuthor());
        languageValue.setText(currentBook.getLanguage());
        genreValue.setText(currentBook.getGenre());
        pagesValue.setText(currentBook.getPages().toString());
        yearValue.setText(currentBook.getRelease().toString());

        if (currentBook.getAvailable()) {
            availableRadio.setSelected(true);
        } else {
            unavailableRadio.setSelected(true);
        }
    }
}

您的代码有些矫枉过正;基本上不需要populateView()方法。当你改变 predicate 时,过滤列表会更新它的内容,并通知观察者它的内容已经改变了。所以你应该直接把列表视图的项目列表设置为过滤列表。然后你的搜索字段的侦听器只需要更新 predicate ,列表视图就会自动更新。
删除populateView()方法并将initialize()方法更新为:

public void initialize(URL arg0, ResourceBundle arg1) {
    // Populate the variable we use throughout the program with the data from the JSON file
    filteredBooks = new FilteredList<Book>(handleJSON.getBooks());
    listView.setItems(filteredBooks);

    // ...
    // ...
    // section of code responsible to check for search changes, when found, fires populateView once more, this time with the variable updated.

    searchField.textProperty().addListener((obs, oldText, newText) -> {
    
        filteredBooks.setPredicate(book -> {
            if(newText == null || newText.isEmpty() || newText.isBlank()) {
                return true;
            }
            String lowerCaseCompare = newText.toLowerCase();
            return book.getTitle().toLowerCase().contains(lowerCaseCompare)
        });
    
    
    }); // Listener
    // ...

    // This one handles the selection of an item in the list, when selected, the fields on the other side of the windows will get populated with the respective data from the book based on the id from the list, since they essentialy share the same FilteredList

    listView.getSelectionModel().selectedItemProperty().addListener(
        (obs, oldSel, newSel) -> populateDetails(newSel)
    );

}

相关问题