JavaFX deleting from TableView

Saucy Goat :

I'm working on a JavaFX app and am using a TableView as part of the GUI as follows:

public TableView<Meal> meals;

And to it I assigned a button that is supposed to delete elements which are selected. To do that I used a function that kept giving me a NoSuchElementException

public void deleteMealButtonClicked()
{
    ObservableList<Meal> mealsSelected = meals.getSelectionModel().getSelectedItems();
    ObservableList<Meal> allMeals = meals.getItems();

    mealsSelected.forEach(allMeals::remove);
}

I debugged my way into finding out the problem was in the last line of the method.

After researching for quite a while, I happened to stumble across a piece of code that solved the problem. Here is the method with the code that works:

public void deleteMealButtonClicked()
{
    ObservableList<Meal> mealsSelected = meals.getSelectionModel().getSelectedItems();
    ObservableList<Meal> allMeals = meals.getItems();

    if (mealsSelected != null) {
        ArrayList<Meal> rows = new ArrayList<>(mealsSelected);
        rows.forEach(row -> meals.getItems().remove(row));
    }
}

My question is, why does the second method work and not the first? Aren't they fundamentally the same thing, other than adding the selected rows to an ArrayList on the second method?

Thank you in advance.

Slaw :

The selection model of the TableView must observe the ObservableList in the items property in order to properly reflect the state of the table. In other words, if an element is removed from items it must also be removed from the selection model's selected items.

This results in you indirectly modifying the selectedMeals list while iterating it to remove the contained elements from the meals list. In a lot of the java.util collections the iterators are fail-fast and this would result in a ConcurrentModificationException; I guess the selection model is not fail-fast and so you simply see indeterminate behavior (in the form of a NullPointerException).

The second option works because you first make a copy of selectedMeals and then iterate the copy.

You ask in a comment for a way to not create a copy; unfortunately, I can't think of one. You could simplify your code a bit by using the removeAll, however.

ObservableList<Meal> selected = ...;

// List.copyOf is a Java 10 method (and doesn't accept null elements)
table.getItems().removeAll(List.copyOf(selected));

// Pre Java 10 you can use toArray since ObservableList overloads removeAll
// with a version that takes a varargs parameter
table.getItems().removeAll(selected.toArray(new Meal[0]));

// or use the copy constructor of many collection classes
table.getItems().removeAll(new ArrayList<>(selected));

// In Java 11, toArray can be replaced with
table.getItems().removeAll(selected.toArray(Meal[]::new));

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=107948&siteId=1