What is the purpose of @NamedArg annotation in javaFX 8?
I would like to know what is the use case of @NamedArg annotation in JavaFX 8
The javadoc does not give us more details, Javadoc : Annotation that provides information about argument's name.
And no more information, documentation, examples on the internet.
Maybe someone could help ?
Regards.
The @NamedArg
annotation allows an FXMLLoader
to instantiate a class that does not have a zero-argument constructor.
Technical Background:
The FXMLLoader
creates objects using reflection. Typically, if you use a tag corresponding to a class with a constructor taking no arguments, an object is created from that class by calling Class.newInstance()
, which invokes the no-argument constructor.
If a class is defined only with constructors that take parameters, then this is problematic. The main issue is that the Java Language Specification does not require the names of parameters (to methods or constructors) to be retained at runtime. This means there's no direct, guaranteed, way for the FXMLLoader
to determine which parameter has a given name.
To make this concrete, suppose we define a Person
class as follows:
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....
}
In FXML we might try to create a Person
as follows:
<Person firstName="Jacob" lastName="Smith"/>
This won't work, because the FXML loader has no guarantee that the runtime representation of the Person
class retains the information as to which constructor parameter is firstName
and which is lastName
.
Historical background
Java 2.2 defined "Builder" classes corresponding to each control. These builder classes follow the standard builder pattern. When the FXMLLoader
encounters a tag referencing a class with no zero-argument constructor, it would use the corresponding builder to create the instance.
Unfortunately, the implementation of the builder classes was flawed, and they were deprecated in JavaFX 8, and will be removed in a later version (probably JavaFX 9). This left a problem for the FXMLLoader
, which would no longer have builder classes to rely on for instantiating classes with no zero-argument constructor. A real example is the Color
class, which has no zero-argument constructor and will have its builder class removed.
@NamedArgs
The fix for this was to introduce an annotation that is used to retain a name of a method (or constructor) argument at runtime. By reflection, we can query the parameter list of a constructor/method, and get the type (but not the name) of each parameter. It is also possible to query each parameter for any annotations, and get the value of those annotations. So the @NamedArg
annotation was introduced specifically for the purpose of retaining a name of a parameter at runtime.
Example
For an example, use the Person
class we introduced above:
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); }
}
If you try to load this using 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());
}
}
then you see an error at runtime:
Caused by: java.lang.NoSuchMethodException: application.Person.<init>()
indicating the FXMLLoader
is looking for a constructor taking no arguments (Person.<init>()
).
In JavaFX 8, you can fix the problem by specifying the name of the parameters with the @NamedArg
annotation:
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); }
}
This will allow the FXMLLoader
to load the class as required.
Note that you can also fix the issue by defining a builder class, and that this also works in JavaFX 2.0 and later. The JavaFX team decided (probably correctly) that using this approach in a way that didn't suffer from the bugs that existed in the initial implementation of the builders would add too much bloat to the codebase of the framework.
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);
}
}
Clearly if you are using JavaFX 8, the constructor annotation approach is much less work.
References:
- Proposal to deprecate builders
- Tweak request to add constructor annotations
- Builder pattern
-
FXML documentation (discusses builders, but not
@NamedArg
) - Request to add documentation on @NamedArgs to "Introduction to FXML" document