@NamedArg 注解允许FXMLLoader 实例化一个没有零参数构造函数的类。
技术背景:
FXMLLoader 使用反射创建对象。通常,如果您使用与具有不带参数的构造函数的类对应的标签,则通过调用 Class.newInstance() 从该类创建一个对象,该类调用无参数构造函数。
如果一个类仅使用带参数的构造函数定义,那么这是有问题的。主要问题是 Java 语言规范不要求在运行时保留参数的名称(对于方法或构造函数)。这意味着FXMLLoader 没有直接的、有保证的方法来确定哪个参数具有给定的名称。
为了具体化,假设我们定义一个Person 类如下:
package application;
import javafx.beans.NamedArg;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName ;
private final StringProperty lastName ;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(this, "firstName", firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
}
// methods....
}
在 FXML 中,我们可能会尝试创建一个 Person,如下所示:
<Person firstName="Jacob" lastName="Smith"/>
这行不通,因为 FXML 加载器无法保证 Person 类的运行时表示会保留关于哪个构造函数参数是 firstName 以及哪个是 lastName 的信息。
历史背景
Java 2.2 定义了与每个控件对应的“Builder”类。这些构建器类遵循标准构建器模式。当FXMLLoader 遇到一个标签引用一个没有零参数构造函数的类时,它会使用相应的构建器来创建实例。
不幸的是,构建器类的实现存在缺陷,they were deprecated in JavaFX 8,将在以后的版本中删除(可能是 JavaFX 9)。这给FXMLLoader 留下了一个问题,它不再需要依赖构建器类来实例化没有零参数构造器的类。一个真实的例子是Color 类,它没有零参数构造函数,并且将删除它的构建器类。
@NamedArgs
解决此问题的方法是引入一个注释,用于在运行时保留方法(或构造函数)参数的名称。通过反射,我们可以查询一个构造函数/方法的参数列表,得到每个参数的类型(但不是名称)。还可以查询每个参数的任何注释,并获取这些注释的值。所以专门引入了@NamedArg注解,目的是为了在运行时保留参数的名称。
示例
例如,使用我们上面介绍的Person类:
package application;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName ;
private final StringProperty lastName ;
public Person(String firstName, String lastName) {
this.firstName = new SimpleStringProperty(this, "firstName", firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
}
public final StringProperty firstNameProperty() { return firstName; }
public final String getFirstName() { return firstNameProperty().get(); }
public final void setFirstName(final String firstName) { firstNameProperty().set(firstName); }
public final StringProperty lastNameProperty() { return lastName; }
public final String getLastName() { return lastNameProperty().get(); }
public final void setLastName(final String lastName) { lastNameProperty().set(lastName); }
}
如果您尝试使用 FXML 加载它:
Person.fxml:
<?xml version="1.0" encoding="UTF-8"?>
<?import application.Person?>
<Person firstName="Jacob" lastName="Smith" xmlns:fx="http://javafx.com/fxml/1" />
Main.java:
package application;
import java.io.IOException;
import javafx.fxml.FXMLLoader;
public class Main {
public static void main(String[] args) throws IOException {
Person person = FXMLLoader.load(Main.class.getResource("Person.fxml"));
System.out.println(person.getFirstName()+" "+person.getLastName());
}
}
然后你会在运行时看到一个错误:
Caused by: java.lang.NoSuchMethodException: application.Person.<init>()
表示FXMLLoader 正在寻找不带参数的构造函数 (Person.<init>())。
在 JavaFX 8 中,您可以通过使用 @NamedArg 注释指定参数名称来解决此问题:
package application;
import javafx.beans.NamedArg;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
public class Person {
private final StringProperty firstName ;
private final StringProperty lastName ;
public Person(@NamedArg("firstName") String firstName, @NamedArg("lastName") String lastName) {
this.firstName = new SimpleStringProperty(this, "firstName", firstName);
this.lastName = new SimpleStringProperty(this, "lastName", lastName);
}
public final StringProperty firstNameProperty() { return firstName; }
public final String getFirstName() { return firstNameProperty().get(); }
public final void setFirstName(final String firstName) { firstNameProperty().set(firstName); }
public final StringProperty lastNameProperty() { return lastName; }
public final String getLastName() { return lastNameProperty().get(); }
public final void setLastName(final String lastName) { lastNameProperty().set(lastName); }
}
这将允许FXMLLoader 根据需要加载类。
请注意,您还可以通过定义构建器类来解决此问题,这也适用于 JavaFX 2.0 及更高版本。 JavaFX 团队决定(可能是正确的)以一种不受构建器初始实现中存在的错误影响的方式使用这种方法会给框架的代码库增加太多的膨胀。
package application;
public class PersonBuilder {
private String firstName ;
private String lastName ;
private PersonBuilder() { }
public static PersonBuilder create() {
return new PersonBuilder();
}
public PersonBuilder firstName(String firstName) {
this.firstName = firstName ;
return this ;
}
public PersonBuilder lastName(String lastName) {
this.lastName = lastName ;
return this ;
}
public Person build() {
return new Person(firstName, lastName);
}
}
显然,如果您使用的是 JavaFX 8,则构造函数注释方法的工作量要少得多。
参考资料: