无法概括JavaFX的TableColumn的一些通用代码

Failed to generalize some common codes for TableColumn of JavaFX

this example for TableView of JavaFX 中,TableColumn 的 3 个实例几乎都在做同样的事情。于是我想写一个新的classSuperColumn extends TableColumn来处理TableColumn的例程。因此,我尝试将通用代码放在构造函数中,如下所示:

public class SuperColumn<Person, V> extends TableColumn<Person, V>{
    String columnHead;
    int columnWidth;
    String variableName;

    public SuperColumn(String columnHead, int columnWidth, String variableName) {
        super();
        this.setText(columnHead);
        this.setMinWidth(columnWidth);      
        this.setCellValueFactory(new PropertyValueFactory<Person, V>(variableName));        
        this.setOnEditCommit(
                new EventHandler<CellEditEvent<Person, V>>() {
                    @Override
                    public void handle(CellEditEvent<Person, V> t) {
                        ((Person) t.getTableView().getItems().get(
                                t.getTablePosition().getRow())
                                ).setEmail(t.getNewValue());
                    }
                });   
    }

}

在上面,我想让setEmail依赖于variableNew。也就是说,如果variableNew"setEmail",那么我们就调用setEmail(t.getNewValue())。如果variableNew"setFirstName",那么我们会调用setFirstName(t.getNewValue()).

我怎样才能做到这一点?

即使只是上面的代码,我也收到错误消息“方法 setEmail(V) 未定义类型 Person”。

public class SuperColumn<Person, V> extends TableColumn<Person, V>{
    public SuperColumn(String columnHead, int columnWidth, String variableName) {
        super();
        this.setText(columnHead);
        this.setMinWidth(columnWidth);
        this.setCellValueFactory(new PropertyValueFactory<>(variableName));
        this.setOnEditCommit(t -> {
            int row = t.getTablePosition().getRow();
            Person person = t.getTableView().getItems().get(row);
            V value = t.getNewValue();
            try {
                Method method = person.getClass().getMethod("set" + variableName, value.getClass());
                method.invoke(person, value);
            } catch ( NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
                e.printStackTrace();
            }

        });
    }

}

并称它为:

SuperColumn<Person, String> column = new SuperColumn<>("Email", 100, "email");

就一般编程风格而言,subclass a class 仅用于配置创建的实例并不是特别好的做法,除非您提供额外的功能基地 class 不提供。如果 subclass 除了有一个构造函数之外什么都不做,这就是这种反模式的一个特殊标志。一种更好的方法(我将在此处展示)是使用某种创建模式,它可能与一些为您创建实例的静态方法一样简单。 (更高级的实现将使用各种工厂模式。)

无论如何,你想要做的事情的一个版本,它不依赖于反射(我稍后会解释为什么我不喜欢反射方法)需要你传递某种处理特定 Person 实例的新(编辑)值的函数。 BiConsumer<Person, V> 适用于此:

public class TableColumns {
    public static <V> TableColumn<Person,V> create(String columnHead, int columnWidth, String variableName, BiConsumer<Person, V> setter) {
        TableColumn<Person, V> column = new TableColumn<>(columnHead);
        column.setMinWidth(columnWidth);
        column.setCellValueFactory(new PropertyValueFactory<>(variableName));
        column.setOnEditCommit(t -> {
            int row = t.getTablePosition().getRow();
            Person person = t.getTableView().getItems().get(row);
            V value = t.getNewValue();
            setter.consume(person, value);
        });
        return column ;
    }

}

您将使用

创建
TableColumn<Person, String> emailColumn = 
    TableColumns.create("Email", 100, "email", Person::setEmail)

请注意,您链接的示例并不是一个特别好的示例(即使它是来自 Oracle 的“官方”示例:它确实非常古老)。使用 JavaFX 属性的模型 class (Person) 应该真正提供“属性 访问器”方法:

public class Person {

    private final StringProperty email = new SimpleStringProperty();

    public StringProperty emailProperty() {
        return email ;
    }

    public final String getEmail() {
        return emailProperty().get();
    }

    public final void setEmail(String email) {
        emailProperty().set(email);
    }

    // similarly for other properties

}

使用此版本的 Person class,您的 table 列只需访问特定的 属性 即可获取值(在 cellValueFactory) 和设置值(在 onEditCommit 处理程序中)。因此,您所需要的只是一个为给定 Person 实例提供 属性 的函数。您还可以轻松地将其概括为 S:

类型的参数化行值
public class TableColumns {

    public static <S,T> TableColumn<S,T> create(String title, int width, Function<S, Property<T>> property) {
        TableColumn<S,T> column = new TableColumn<>(title);
        column.setMinWidth(width);
        column.setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        column.setOnEditCommit(t -> {
            int row = t.getTablePosition().getRow();
            S rowValue = t.getTableView().getItems().get(row);
            T value = t.getNewValue();
            property.apply(rowValue).setValue(value);
        });
        return column ;
    }

    // other creational methods...
}

现在你可以

TableColumn<Person, String> emailColumn = 
    TableColumns.create("Email", 100, Person::emailProperty);

这种方法既避免了直接使用反射,也避免了使用遗留的 PropertyValueFactory(它在幕后使用类似的基于反射的方法)。这里的主要好处是编译器现在将检查是否存在适当的方法,并且在编译时捕获任何错误。 (相比之下,当使用反射指定方法名称时,或 PropertyValueFactory 中的 属性 名称时,错误无法被编译器捕获,它只会在运行时失败,要么静默(没有数据出现)或有例外。)

只是为了完整起见,如果你真的想为此使用子class,它看起来像

public class SuperColumn<S,T> extends TableColumn<S,T> {

    public SuperColumn(String title, int width, Function<S, Property<T>> property) {
        super(title);
        setMinWidth(width);
        setCellValueFactory(cellData -> property.apply(cellData.getValue()));
        setOnEditCommit(event -> {
            int row = event.getTablePosition().getRow();
            S rowValue = event.getTableView().getItems().get(row);
            T value = event.getNewValue();
            property.apply(rowValue).setValue(value);
        });
    }
}

但我要再次强调,这不是继承的好用法,您应该更喜欢基于方法(或基于工厂)的方法。

最后我设法结合了 Lê Hoàng Dững 的想法(即使用 Function<S, Property<T>>)和 James_D(即使用 TableColumns.Create()),我得到:

import java.util.function.Function;

import javafx.beans.property.Property;
import javafx.scene.control.TableColumn;

public class TableColumns<S,T> {

    public static <S, T> TableColumn<S,T> create(String columnHead, int columnWidth, Function<S, Property<T>> myFunc) {
    
        TableColumn<S, T> myColumn = new TableColumn<>(columnHead);

        myColumn.setMinWidth(columnWidth);
        myColumn.setCellValueFactory(cellData -> myFunc.apply(cellData.getValue())); // binding column (i.e. this) and the variable
        myColumn.setOnEditCommit(event -> {
            int row = event.getTablePosition().getRow();
            S rowValue = event.getTableView().getItems().get(row);
            T newValue = event.getNewValue();
            myFunc.apply(rowValue).setValue(newValue);
        });
        return myColumn;
    }

}