Exemple de création d'un objet à l'aide de Class.newInstance()

Imaginez que vous êtes chargé de créer un objet en utilisant la réflexion. Allons-nous commencer?

Nous allons commencer par écrire le code de la classe que nous voulons instancier :


public class Employee {
    private String name;
    private String lastName;
    private int age;

    {
        age = -1;
        name = "Rob";
        surname = "Stark";
    }

    public Employee(String name, String surname, int age) {
        this.name = name;
        this.surname = surname;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSurname() {
        return lastName;
    }

    public void setSurname(String surname) {
        this.surname = surname;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Employee{" +
                "name='" + name + '\'' +
                ", surname='" + surname + '\'' +
                ", age=" + age +
                '}';
    }
}

Ce sera notre classe — avec plusieurs champs, un constructeur avec des paramètres, des getters et des setters, une méthode toString() et un bloc d'initialisation. Passons maintenant à la deuxième partie : créer un objet en utilisant la réflexion. La première approche que nous examinerons utilisera Class.newInstance() .


public class Main {
    public static void main(String[] args) throws InstantiationException, IllegalAccessException {
        Employee employee = Employee.class.newInstance();
        System.out.println("age is " + employee.getAge());
    }
}

Excellent! Exécutons notre code et surveillons l'âge à afficher. Mais nous obtenons une erreur concernant un constructeur par défaut manquant. Il s'avère que cette méthode nous permet uniquement d'obtenir un objet créé à l'aide d'un constructeur par défaut. Ajoutons un constructeur par défaut pour notre classe et testons à nouveau le code.

Message d'erreur:

Code du nouveau constructeur


public Employee() { }

Après avoir ajouté le constructeur, voici le résultat :

l'âge est de 1

Super! Nous avons compris comment cette méthode fonctionne. Voyons maintenant sous le capot. En ouvrant la documentation, nous voyons que notre méthode est déjà obsolète :

Il peut également lancer InstantiationException et IllegalAccessException . En conséquence, la documentation suggère que nous utilisions l'autre manière de créer un objet, à savoir Constructor.newInstance() . Analysons en détail le fonctionnement de la classe Constructor .

Méthodes getConstructors et getDeclaredConstructors

Pour travailler avec la classe Constructor , nous devons d'abord obtenir une instance. Nous avons deux méthodes pour cela : getConstructors et getDeclaredConstructors .

Le premier renvoie un tableau de constructeurs publics et le second renvoie un tableau de tous les constructeurs de classe.

Donnons à notre classe un peu de confidentialité, ou plutôt, créons des constructeurs privés pour aider à démontrer comment ces méthodes fonctionnent.

Ajoutons quelques constructeurs privés :


private Employee(String name, String surname) {
    this.name = name;
    this.lastName = lastName;
}

En regardant le code, notez que l'un des constructeurs est privé :

Testons nos méthodes :


public class Main {
	  public static void main(String[] args) {
	      Class employeeClass = Employee.class;
	
	      System.out.println("getConstructors:");
	      printAllConstructors(employeeClass);
	
	      System.out.println("\n" +"getDeclaredConstructors:");
	      printDeclaredConstructors(employeeClass);
	  }
	
	  static void printDeclaredConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getDeclaredConstructors()) {
	          System.out.println(constructor);
	      }
	  }
	
	  static void printAllConstructors(Class<?> c){
	      for (Constructor<?> constructor : c.getConstructors()) {
	          System.out.println(constructor);
	      }
	  }
}

Et nous obtenons ce résultat :

getConstructors :
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public.com.codegym.Employee()

getDeclaredConstructors :
privé com.codegym.Employee(java.lang.String,java.lang .String)
public com.codegym.Employee(java.lang.String,java.lang.String,int)
public com.codegym.Employee()

D'accord, c'est ainsi que nous obtenons l'accès à l' objet Constructor . Maintenant, nous pouvons parler de ce qu'il peut faire.

La classe java.lang.reflect.Constructor et ses méthodes les plus importantes

Jetons un coup d'œil aux méthodes les plus importantes et à leur fonctionnement :

Méthode Description
obtenirNom() Renvoie le nom de ce constructeur sous forme de chaîne.
getModifiers() Renvoie les modificateurs d'accès Java encodés sous forme de nombre.
getExceptionTypes() Retourne un tableau d'objets Class qui représentent les types d'exceptions déclarées par le constructeur.
getParameters() Renvoie un tableau d' objets Parameter représentant tous les paramètres. Retourne un tableau de longueur 0 si le constructeur n'a pas de paramètres.
getParameterTypes() Retourne un tableau d'objets Class qui représentent les types de paramètres formels dans l'ordre de déclaration.
getGenericParameterTypes() Retourne un tableau d'objets Type qui représentent les types de paramètres formels dans l'ordre de déclaration.

getName() & getModifiers()

Enveloppons notre tableau dans une liste pour qu'il soit pratique de travailler avec. Nous écrirons également les méthodes getName et getModifiers :


static List<Constructor<?>> getAllConstructors(Class<?> c) {
    return new ArrayList<>(Arrays.asList(c.getDeclaredConstructors()));
}

static List<String> getConstructorNames(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(constructor.toString());
    }
    return result;
}

static List<String> getConstructorModifiers(List<Constructor<?>> constructors) {
    List<String> result = new ArrayList<>();
    for (Constructor<?> constructor : constructors) {
        result.add(Modifier.toString(constructor.getModifiers()));
    }
    return result;
}

Et notre méthode principale , où nous appellerons tout :


public static void main(String[] args) {
    Class employeeClass = Employee.class;
    var constructors = getAllConstructors(employeeClass);
    var constructorNames = getConstructorNames(constructors);
    var constructorModifiers = getConstructorModifiers(constructors);

    System.out.println("Employee class:");
    System.out.println("Constructors :");
    System.out.println(constructorNames);
    System.out.println("Modifiers :");
    System.out.println(constructorModifiers);
}

Et maintenant nous voyons toutes les informations que nous voulons :

Classe d'employé :
Constructeurs :
[private com.codegym.Employee(java.lang.String), public
com.codegym.Employee(java.lang.String,java.lang.String,int), public com.codegym.Employee() ]
Modificateurs :
[privé, public, public]

getExceptionTypes()

Cette méthode nous permet d'obtenir un tableau des exceptions que notre constructeur peut lever. Modifions l'un de nos constructeurs et écrivons une nouvelle méthode.

Ici, nous modifions légèrement notre constructeur actuel :


private Employee(String name, String surname) throws Exception {
    this.name = name;
    this.lastName = lastName;
}

Et ici, nous avons une méthode pour obtenir des types d'exception et l'ajouter à main :


static List<Class<?>> getConstructorExceptionTypes(Constructor<?> c) {
      return new ArrayList<>(Arrays.asList(c.getExceptionTypes()));
}


var constructorExceptionTypes = getConstructorExceptionTypes(constructors.get(0));
System.out.println("Exception types :");
System.out.println(constructorExceptionTypes);

Ci-dessus, nous avons accédé au premier constructeur de notre liste. Nous verrons comment obtenir un constructeur spécifique un peu plus tard.

Et regardez la sortie après avoir ajouté throws Exception :

Types d'exception :
[classe java.lang.Exception]

Et avant d'ajouter l'exception :

Types d'exception :
[]

Tout est merveilleux, mais comment voyons-nous quels paramètres sont requis par nos constructeurs ? Comprenons cela aussi.

getParameters() & getParameterTypes() & getGenericParameterTypes()

Recommençons par peaufiner notre constructeur privé. Maintenant, cela ressemblera à ceci :


private Employee(String name, String surname, List<String> list) {
    this.name = name;
    this.lastName = lastName;
}

Et nous avons trois méthodes supplémentaires : getParameters pour obtenir l'ordre des paramètres et leurs types, getParameterTypes pour obtenir les types de paramètres et getGenericParameterTypes pour obtenir les types enveloppés dans des génériques .


static List<Parameter> getConstructorParameters(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameters()));
}

static List<Class<?>> getConstructorParameterTypes(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getParameterTypes()));
}

static List<Type> getConstructorParametersGenerics(Constructor<?> c) {
    return new ArrayList<>(Arrays.asList(c.getGenericParameterTypes()));
}

Et nous ajoutons quelques informations supplémentaires à notre méthode principale déjà pas si petite :


var constructorParameterTypes = getConstructorParameterTypes(constructors.get(0));
var constructorParameters = getConstructorParameters(constructors.get(0));
var constructorParametersGenerics = getConstructorParametersGenerics(constructors.get(0));

System.out.println("Constructor parameters :");
System.out.println(constructorParameters);

System.out.println("Parameter types :");
System.out.println(constructorParameterTypes);

System.out.println("Constructor parameter types :");
System.out.println(constructorParametersGenerics);

En regardant la sortie, nous voyons des informations très détaillées sur les paramètres de nos constructeurs :

Paramètres constructeur :
[java.lang.String arg0, java.lang.String arg1, java.util.List<java.lang.String> arg2]
Types de paramètres :
[class java.lang.String, class java.lang.String, interface java.util.List]
Types de paramètres du constructeur :
[class java.lang.String, class java.lang.String, java.util.List<java.lang.String>]

Cela démontre clairement la différence entre chaque méthode. Nous voyons que nous avons des options distinctes pour obtenir des informations sur les types de paramètres, les types enveloppés et tout en général. Super! Maintenant que nous connaissons la classe Constructor , nous pouvons revenir au sujet principal de notre article : la création d'objets.

Création d'un objet à l'aide de Constructor.newInstance()

La deuxième façon de créer des objets consiste à appeler la méthode newInstance sur le constructeur. Jetons un coup d'œil à un exemple de travail et voyons comment nous pouvons obtenir un constructeur particulier.

Si vous souhaitez obtenir un seul constructeur, vous devez utiliser la méthode getConstructor (à ne pas confondre avec getConstructors , qui renvoie un tableau de tous les constructeurs). La méthode getConstructor renvoie le constructeur par défaut.


public static void main(String[] args) throws NoSuchMethodException {
    Class employeeClass = Employee.class;
    Constructor<?> employeeConstructor = employeeClass.getConstructor();
    System.out.println(employeeConstructor);
}
public com.codegym.Employee()

Et si nous voulons obtenir un constructeur spécifique, nous devons passer les types de paramètres du constructeur à cette méthode.

N'oubliez pas que nous ne pouvons obtenir notre constructeur privé qu'en utilisant la méthode getDeclaredConstructor .


Constructor<?> employeeConstructor2 = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(employeeConstructor2);

C'est ainsi que nous pouvons obtenir un constructeur spécifique. Essayons maintenant de créer des objets en utilisant des constructeurs privés et publics.

Constructeur public :


Class employeeClass = Employee.class;
Constructor<?> employeeConstructor = employeeClass.getConstructor(String.class, String.class, int.class);
System.out.println(employeeConstructor);

Employee newInstance = (Employee) employeeConstructor.newInstance("Rob", "Stark", 10);
System.out.println(newInstance);

Le résultat est un objet avec lequel nous pouvons travailler :

public com.codegym.Employee(java.lang.String,java.lang.String,int)
Employee{name='Rob' nom='Stark', age=10}

Tout fonctionne très bien ! Essayons maintenant avec un constructeur privé :


Constructor<?> declaredConstructor = employeeClass.getDeclaredConstructor(String.class, String.class, List.class);
System.out.println(declaredConstructor);

Employee newInstance2 = (Employee) declaredConstructor.newInstance("Rob", "Stark", new ArrayList<>());
System.out.printf(newInstance2.toString());

Le résultat est une erreur sur la confidentialité de notre constructeur :

Java n'a pas pu créer d'objet en utilisant ce constructeur, mais il y a en fait quelque chose de magique que nous pouvons faire dans la méthode main . Nous pouvons définir le niveau d'accès de notre constructeur, permettant de créer des objets de notre classe :


declaredConstructor.setAccessible(true);

Résultat de la création d'un objet

privé com.codegym.Employee(java.lang.String,java.lang.String,java.util.List)
Employé{nom='Rob', nom='Stark', age=-1}

Nous ne définissons pas l'âge dans notre constructeur, il reste donc le même que lorsqu'il a été initialisé.

Merveilleux, résumons-nous !

Avantages de la création d'objets à l'aide de Constructor.newInstance()

Les deux méthodes partagent le même nom, mais il existe des différences entre elles :

Class.newInstance() Constructor.newInstance()
Ne peut appeler qu'un constructeur sans argument . Peut appeler n'importe quel constructeur quel que soit le nombre de paramètres.
Nécessite que le constructeur soit visible. Peut également faire appel à des constructeurs privés dans certaines circonstances.
Lève toute exception (cochée ou non) déclarée par le constructeur. Enveloppe toujours une exception levée avec InvocationTargetException .

Pour ces raisons, Constructor.newInstance() est préféré à Class.newInstance() et est la méthode utilisée par divers frameworks et API tels que Spring, Guava, Zookeeper, Jackson, Servlet, etc.