javaFX 8 中@NamedArg 注释的目的是啥?

Posted

技术标签:

【中文标题】javaFX 8 中@NamedArg 注释的目的是啥?【英文标题】:What is the purpose of @NamedArg annotation in javaFX 8?javaFX 8 中@NamedArg 注释的目的是什么? 【发布时间】:2015-01-05 12:55:09 【问题描述】:

我想知道JavaFX 8中@NamedArg注解的用例是什么

javadoc 没有给我们更多细节, Javadoc : 提供参数名称信息的注解。

互联网上没有更多信息、文档和示例。

也许有人可以帮忙?

问候。

【问题讨论】:

感谢您提出这个问题。在我看到这个并研究答案之前,我不知道@NamedArgs。我一直想知道 FXMLLoader 依赖于构建器的问题在移除构建器后将如何得到解决,这也回答了这个问题。 有关 FXML 注释的完整列表,请查看 abhinay.xyz/javafx/2018/05/09/… 【参考方案1】:

@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.&lt;init&gt;())。

在 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,则构造函数注释方法的工作量要少得多。

参考资料:

Proposal to deprecate builders Tweak request to add constructor annotations Builder pattern FXML documentation(讨论建设者,但不讨论@NamedArg) Request to add documentation on @NamedArgs to "Introduction to FXML" document

【讨论】:

非常感谢您提供完整和图解的答案,直到现在我必须为我的 JavaFX bean 定义空的无参数构造函数,它将使用 @NamedArg 更改和清理我的代码。 @James_D 感谢您的详尽解释!只是好奇,4 年多之后,BuilderBuilderFactory 终于被弃用了吗?以及为什么Request to add documentation on @NamedArgs to "Introduction to FXML" document 被标记为 wontfix(阅读文档时让我很困惑)?

以上是关于javaFX 8 中@NamedArg 注释的目的是啥?的主要内容,如果未能解决你的问题,请参考以下文章

生成int列表以在JavaFX中填充ComboBox

子节点的 JavaFX GridPane 动态调整大小以填充分配的区域

JavaFX 8:舞台插图(窗户装饰厚度)?

JavaFX 8 DatePicker 风格

Javafx 8:在初始化方法中填充 TableView

javafx 8在textarea中编辑文本