Adnotacja umożliwia FXMLLoader
tworzenie instancji klasy, która nie ma konstruktora zerowego argumentu.
Tło techniczne:
FXMLLoader
tworzy obiekty przy użyciu odbicia. Zwykle, jeśli używasz znacznika odpowiadającego klasie z konstruktorem nie pobierającym żadnych argumentów, obiekt jest tworzony z tej klasy przez wywołanie Class.newInstance()
, które wywołuje konstruktor bezargumentowy.
Jeśli klasa jest zdefiniowana tylko z konstruktorami, które pobierają parametry, jest to problematyczne. Głównym problemem jest to, że specyfikacja języka Java nie wymaga, aby nazwy parametrów (do metod lub konstruktorów) były zachowywane w środowisku wykonawczym. Oznacza to, że nie ma bezpośredniego, gwarantowanego sposobu, aby FXMLLoader
określić, który parametr ma daną nazwę.
Aby to beton, załóżmy, że zdefiniowanie klasy Person
następująco:
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....
}
W FXML możemy spróbować stworzyć Person
następująco:
<Person firstName="Jacob" lastName="Smith"/>
To nie zadziała, ponieważ moduł ładujący FXML nie ma gwarancji, że reprezentacja środowiska wykonawczego klasy Person
zachowuje informacje o tym, który parametr konstruktora to firstName
i który jest lastName
.
historyczny
Java 2.2 zdefiniowanych klas "Builder" odpowiadające każdej kontroli. Te klasy konstruktora stosują standardowy wzorzec budowania. Gdy FXMLLoader
napotka znacznik odwołujący się do klasy bez konstruktora zerowego argumentu, użyje odpowiedniego konstruktora do utworzenia instancji.
Niestety, implementacja klas budowniczych była błędna, i they were deprecated in JavaFX 8, i zostaną usunięte w późniejszej wersji (prawdopodobnie JavaFX 9). To pozostawiło problem dla FXMLLoader
, który nie będzie już miał klas budowniczych, na których można polegać przy tworzeniu instancji klas bez konstruktora zerowego argumentu. Prawdziwym przykładem jest klasa Color
, która nie ma konstruktora zerowego argumentu i zostanie usunięta jego klasa budowniczych.
@NamedArgs
Rozwiązaniem tego było wprowadzać adnotacje, która jest używana do zatrzymywania się nazwą metody (lub konstruktora) argument w czasie pracy.Przez odbicie możemy zapytać listę parametrów konstruktora/metody i uzyskać typ (ale nie nazwę) każdego parametru. Możliwe jest również zapytanie do każdego parametru o dowolne adnotacje i uzyskanie wartości tych adnotacji. Tak więc adnotacja @NamedArg
została wprowadzona specjalnie w celu zachowania nazwy parametru w środowisku wykonawczym.
Przykład
Na przykład użyć klasy Person
wprowadziliśmy powyżej:
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); }
}
Jeśli próbujesz załadować korzystając 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());
}
}
potem widzisz błąd w czasie wykonywania:
Caused by: java.lang.NoSuchMethodException: application.Person.<init>()
wskazujące na FXMLLoader
poszukuje konstruktora biorąc żadnych argumentów (Person.<init>()
).
W JavaFX 8, można rozwiązać ten problem poprzez podanie nazwy parametrów z @NamedArg
adnotacji:
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); }
}
To pozwoli FXMLLoader
załadować klasę zgodnie z wymaganiami.
Należy pamiętać, że można również rozwiązać ten problem, definiując klasę konstruktora i działa to również w JavaFX 2.0 i nowszych wersjach. Zespół JavaFX zdecydował (prawdopodobnie poprawnie), że zastosowanie tego podejścia w sposób, który nie ucierpiałby na błędach, które istniały w początkowej fazie implementacji budowniczych, spowodowałoby zbyt duże obciążenie bazy kodu.
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);
}
}
Oczywiście, jeśli używasz JavaFX 8, podejście do opisu konstruktora jest znacznie mniej pracy.
Odniesienia:
Dziękuję za to pytanie. Nie wiedziałem o '@ NamedArgs', dopóki nie zobaczyłem tego i nie przeszedłem odpowiedzi. Zastanawiałem się, w jaki sposób problem z 'FXMLLoader' polegający na budowaniu zostanie rozwiązany po usunięciu budowniczych, i to też odpowiada na to pytanie. –