我正在为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只是为了确保,我仍然得到相同的堆栈,任何想法是什么可能导致这一点?
1条答案
按热度按时间yshpjwxd1#
堆栈跟踪会告诉您问题的确切原因。当您在
populateDetails(...)
方法中的MainViewController.java
的第200行使用值-1
对x1m2 n1 a调用get(..)
时,会出现ArrayIndexOutOfBoundsException
。查看您的代码,此行一定是因此
selectedBookID
一定是罪魁祸首,其值为-1
。selectedBookID
是一个传递给该方法的参数,您可以在initialize()
方法的lambda表达式中从MainController.java
的第127行调用该方法。(同样,此信息位于堆栈跟踪中。)您传递的值为文档会明确告诉您发生这种情况的时间:
选定的索引为-1(表示没有选择)或在基础数据模型大小范围内的整数值。
因此,您的填充细节需要处理未选择任何内容的情况(可能是通过清除文本字段)。我认为监听
selectedItemProperty()
而不是selectedIndexProperty()
会更干净,因为它会直接给您选择的Book
(如果未选择任何内容,则为null
),并且您不必从列表中检索Book
:您的代码有些矫枉过正;基本上不需要
populateView()
方法。当你改变 predicate 时,过滤列表会更新它的内容,并通知观察者它的内容已经改变了。所以你应该直接把列表视图的项目列表设置为过滤列表。然后你的搜索字段的侦听器只需要更新 predicate ,列表视图就会自动更新。删除
populateView()
方法并将initialize()
方法更新为: