JavaFX TableView填充报错Cannot read from unreadable property及关联对象属性问题
*PropertyValueFactory*的取值逻辑严格遵循JavaBean规范:传入属性名后,它会优先查找实体类中对应命名的getter方法(例如传入"id"就匹配调用getId()),无法找到符合规范的getter时,不会直接读取类字段,会返回空值,最终所有单元格无内容显示。
你当前的Student、Book类仅定义了字段和构造方法,未提供任何getter方法,因此所有列都无法正常获取值。你之前使用相同方法实现成功,大概率是当时的实体类已经配套写好了符合规范的getter方法,这次编写类时遗漏了该部分,因此出现了填充失败的问题。
另外你使用了原生类型TableView tableView = new TableView();(未指定泛型TableView<Student>),虽不直接导致填充失败,但会引发类型警告,后续扩展时容易出现逻辑错误。
同时当前favoriteBookCol泛型声明为TableColumn<Student, Book>,就算正常取到Book对象,也会因为Book类未重写toString()方法,默认显示对象哈希地址,无法满足展示需求。
如果要在列中直接展示关联对象的属性(例如favoriteBook的title),有两种常用实现方式:
- 方案1:自定义
CellValueFactory回调,无需修改现有实体类结构,适合嵌套属性场景,灵活性最高 - 方案2:继续使用
PropertyValueFactory,只需在Student类中新增对应属性的getter方法即可,写法更简洁
1. 补全规范getter的实体类
public class Student { private Integer id; private String name; private Book favoriteBook; public Student(Integer id, String name, Book favoriteBook) { this.id = id; this.name = name; this.favoriteBook = favoriteBook; } // 基础getter,供PropertyValueFactory取值 public Integer getId() { return id; } public String getName() { return name; } public Book getFavoriteBook() { return favoriteBook; } // 方案2专用:返回书名的getter public String getFavoriteBookTitle() { return favoriteBook == null ? "" : favoriteBook.getTitle(); } } public class Book { private String title; public Book(String title) { this.title = title; } public String getTitle() { return title; } }
2. 修改后的Main类(以自定义回调方案1为例)
import javafx.beans.property.SimpleStringProperty; import javafx.collections.FXCollections; import javafx.collections.ObservableList; import javafx.scene.Scene; import javafx.scene.control.TableColumn; import javafx.scene.control.TableView; import javafx.scene.control.cell.PropertyValueFactory; import javafx.scene.layout.HBox; import javafx.stage.Stage; public class Main extends Application { Student student1 = new Student(0, "Jennifer", new Book("JavaFX for Dummies")); Student student2 = new Student(1, "John", new Book("Cooking with Jamie Oliver")); Student student3 = new Student(2, "Barbara", new Book("Jokes from Germany")); ObservableList<Student> studentsObservable = FXCollections.observableArrayList(student1, student2, student3); public static void main(String[] args) { launch(args); } @Override public void start(Stage primaryStage) throws Exception { TableColumn<Student, Integer> idCol = new TableColumn<>("id"); TableColumn<Student, String> nameCol = new TableColumn<>("name"); // 列泛型改为String,对应要展示的书名字段类型 TableColumn<Student, String> favoriteBookCol = new TableColumn<>("favoriteBook"); // 给TableView加泛型,消除原生类型警告 TableView<Student> tableView = new TableView<>(); tableView.getColumns().setAll(idCol, nameCol, favoriteBookCol); idCol.setCellValueFactory(new PropertyValueFactory<>("id")); nameCol.setCellValueFactory(new PropertyValueFactory<>("name")); // 自定义CellValueFactory直接取嵌套的书名属性 favoriteBookCol.setCellValueFactory(cellData -> { Student student = cellData.getValue(); Book book = student.getFavoriteBook(); // 处理book为null的边界情况,避免空指针 return new SimpleStringProperty(book == null ? "" : book.getTitle()); }); tableView.setItems(studentsObservable); HBox root = new HBox(); root.getChildren().add(tableView); Scene scene = new Scene(root); primaryStage.setScene(scene); primaryStage.show(); } }
如果选择方案2(继续用PropertyValueFactory),只需将上述代码中favoriteBookCol.setCellValueFactory(...)的部分替换为:
favoriteBookCol.setCellValueFactory(new PropertyValueFactory<>("favoriteBookTitle"));
无需额外写自定义回调即可正常展示书名。
内容的提问来源于stack exchange,提问作者MagisterInformaticus

