javafxbindingtableviewinvocationtargetexceptionunsupportedoperation

Javafx Binding throw exception in application start() method


UnsupportedOperationException thrown in Bindings.bindContent() when bind the objects to TableView. Why? How to resolve this problem?

I am using java 8 update181.

 Exception in Application start method java.lang.reflect.InvocationTargetException
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at com.sun.javafx.application.LauncherImpl.launchApplicationWithArgs(LauncherImpl.java:389)
    at com.sun.javafx.application.LauncherImpl.launchApplication(LauncherImpl.java:328)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at sun.launcher.LauncherHelper$FXHelper.main(LauncherHelper.java:767)
 Caused by: java.lang.RuntimeException: Exception in Application start method
    at com.sun.javafx.application.LauncherImpl.launchApplication1(LauncherImpl.java:917)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication$154(LauncherImpl.java:182)
    at java.lang.Thread.run(Thread.java:748)
 Caused by: java.lang.UnsupportedOperationException
    at java.util.AbstractList.remove(AbstractList.java:161)
    at java.util.AbstractList$Itr.remove(AbstractList.java:374)
    at java.util.AbstractList.removeRange(AbstractList.java:571)
    at java.util.AbstractList.clear(AbstractList.java:234)
    at com.sun.javafx.binding.ContentBinding.bind(ContentBinding.java:55)
    at javafx.beans.binding.Bindings.bindContent(Bindings.java:1020)
    at problem_bind.Main.start(Main.java:42)
    at com.sun.javafx.application.LauncherImpl.lambda$launchApplication1$161(LauncherImpl.java:863)
    at com.sun.javafx.application.PlatformImpl.lambda$runAndWait$174(PlatformImpl.java:326)
    at com.sun.javafx.application.PlatformImpl.lambda$null$172(PlatformImpl.java:295)
    at java.security.AccessController.doPrivileged(Native Method)
    at com.sun.javafx.application.PlatformImpl.lambda$runLater$173(PlatformImpl.java:294)
    at com.sun.glass.ui.InvokeLaterDispatcher$Future.run(InvokeLaterDispatcher.java:95)
    at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
    at com.sun.glass.ui.win.WinApplication.lambda$null$147(WinApplication.java:177)
    ... 1 more Exception running application problem_bind.Main

My codes:

package problem_bind;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

import javafx.application.Application;
import javafx.beans.binding.Bindings;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.scene.control.cell.TextFieldTableCell;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;


public class Main extends Application {

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

    @Override
    public void start(Stage primaryStage)  {
        House house = new House();

        TableView<Person> table = new TableView<>();
        table.setEditable(true);

        TableColumn<Person, String> firstNameColumn = createColumn("First Name", "firstName");
        TableColumn<Person, String> lastNameColumn = createColumn("Last Name", "lastName");
        table.getColumns().add(firstNameColumn);
        table.getColumns().add(lastNameColumn);

        ObservableList<Person> data = FXCollections.observableArrayList();
        data.addAll(house.getPersons());
        table.setItems(data);
        Bindings.bindContent(house.getPersons(), table.getItems());

        BorderPane root = new BorderPane(table, null, null, null, null);
        root.setPadding(new Insets(10));
        primaryStage.setScene(new Scene(root, 600, 600));
        primaryStage.show();
    }


    private TableColumn<Person, String> createColumn(String title, String property) {
        TableColumn<Person, String> col = new TableColumn<>(title);
        col.setSortable(false);
        col.setCellValueFactory(
                new PropertyValueFactory<Person, String>(property));
        col.setCellFactory(TextFieldTableCell.forTableColumn());
        return col ;
    }

    public static class House {
        private List<Person> persons = new ArrayList<Person>();

        public House() {
            this.persons = Arrays.asList(
                    new Person("Jacob", "Smith", this),
                    new Person("Isabella", "Johnson", this),
                    new Person("Ethan", "Williams", this),
                    new Person("Emma", "Jones", this),
                    new Person("Michael", "Brown", this));
        }

        public List<Person> getPersons() {
            return persons;
        }
    }

    public static class Person {
       private String firstName ;
       private String lastName ;
       private House house;

       public Person(String firstName, String lastName, House house) {
           this.firstName = firstName ;
           this.lastName = lastName ;
           this.house = house;
       }

       public String getFirstName() {
           return firstName;
       }

       public void setFirstName(String firstName) {
           this.firstName = firstName;
       }

       public String getLastName() {
           return lastName;
       }

       public void setLastName(String lastName) {
           this.lastName = lastName;
        }

     }
}

Solution

  • The problem is your use of Arrays.asList(Object...). That method:

    Returns a fixed-size list backed by the specified array.

    Because of this, the returned List does not support operations such as add or remove. Instead, you'd modify such a list through the set method. The former two methods inherently require a variable-sized list. The implementation of Bindings.bindContent apparently tries to use those unsupported methods when synchronizing the lists.

    Change this:

    this.persons = Arrays.asList(
            new Person("Jacob", "Smith", this),
            new Person("Isabella", "Johnson", this),
            new Person("Ethan", "Williams", this),
            new Person("Emma", "Jones", this),
            new Person("Michael", "Brown", this));
    

    To this:

    persons = new ArrayList<>();
    persons.add(new Person("Jacob", "Smith", this));
    persons.add(new Person("Isabella", "Johnson", this));
    persons.add(new Person("Ethan", "Williams", this));
    persons.add(new Person("Emma", "Jones", this));
    persons.add(new Person("Michael", "Brown", this));
    

    Note: In your current code, prefixing with this is unnecessary. But you can still use it if you prefer.

    Note #2: See @Pagbo's comment.

    Basically, make sure you use a List which supports the add and remove operations.


    You may be wondering why calling Bindings.bindContent(List,ObservableList) is leading to a remove call in the first place. This is because that method returns:

    A content binding [ensuring] that the List contains the same elements as the ObservableList. If the content of the ObservableList changes, the List will be updated automatically.

    In other words, the List is updated to match the ObservableList. This involves, when first creating the binding, clearing the List then adding all the elements of the ObservableList to it.