W3docs

Java Reflection : Appeler des constructeurs

Instanciez des classes Java par réflexion avec Constructor.newInstance et Class.getDeclaredConstructor.

Créer un objet sans écrire new est l'astuce réflexive qui se cache derrière tout conteneur d'injection de dépendances, désérialiseur et chargeur de plugins : vous disposez d'une Class et vous avez besoin d'une instance. L'objet Constructor<T> représente un constructeur et crée des instances avec newInstance(args...). Ce chapitre explique comment trouver des constructeurs, les appeler avec des arguments, accéder aux constructeurs private, et pourquoi le raccourci Class.newInstance() est déprécié.

Si vous débutez en reflection, commencez par l'introduction à la reflection, puis revenez ici. Les mécanismes décrits ci-dessous correspondent à ce que vous avez vu pour appeler des méthodes et lire des champs par réflexion.

Trouver des constructeurs

Les constructeurs sont recherchés par types de paramètres uniquement — il n'y a pas de nom, car tous les constructeurs partagent le nom de la classe :

Class<User> c = User.class;

Constructor<User> noArg  = c.getDeclaredConstructor();                  // ()
Constructor<User> twoArg = c.getDeclaredConstructor(String.class, int.class);  // (String, int)

Constructor<?>[] pub  = c.getConstructors();          // public only
Constructor<?>[] all  = c.getDeclaredConstructors();  // any access level

Comme partout en reflection, les types de paramètres doivent correspondre exactement (int.class, pas Integer.class), et getConstructor ne voit que les constructeurs public, tandis que getDeclaredConstructor voit aussi les private/protected/package. Notez que Constructor<T> est générique sur la classe qu'il construit, donc newInstance retourne un T typé (contrairement au Object brut de Method.invoke).

Créer des instances avec newInstance

Constructor<User> ctor = User.class.getDeclaredConstructor(String.class, int.class);
User u = ctor.newInstance("ada", 36);     // returns a typed User

Les arguments fonctionnent exactement comme avec Method.invoke : un tableau varargs Object[], les primitives étant autoboxées. La différence est qu'il n'y a pas d'objet cible — un constructeur crée la cible. Les exceptions levées par le corps du constructeur sont enveloppées dans InvocationTargetException, exactement comme pour les méthodes ; déroulez-les avec getCause().

Accéder aux constructeurs privés

Les singletons, les classes utilitaires et les builders cachent souvent leur constructeur. La reflection contourne cela facilement avec setAccessible(true) :

Constructor<Singleton> ctor = Singleton.class.getDeclaredConstructor();
ctor.setAccessible(true);                 // bypass the private modifier
Singleton fresh = ctor.newInstance();     // a SECOND instance — breaks the singleton!

C'est à la fois puissant et dangereux : cela annule la garantie du singleton, le contrat « pas d'instances » d'une classe utilitaire, et tout invariant que le constructeur protégeait. (Un singleton enum est la seule forme que la reflection ne peut pas instancier — Constructor.newInstance refuse explicitement les types enum avec IllegalArgumentException, ce qui explique en partie pourquoi le « singleton enum » est le modèle recommandé.)

Pourquoi Class.newInstance() est déprécié

Vous verrez dans l'ancien code le raccourci clazz.newInstance() :

User u = User.class.newInstance();   // DEPRECATED since Java 9

Il est déprécié pour deux raisons concrètes :

  1. Il appelle uniquement le constructeur sans argument. Impossible de passer des arguments.
  2. Il gère mal les exceptions. Si le constructeur sans argument lève une exception vérifiée, Class.newInstance() la propage sans la déclarer — ce qui contourne l'analyse des exceptions vérifiées par le compilateur.

Le remplacement est toujours :

User u = User.class.getDeclaredConstructor().newInstance();

C'est une ligne de plus, cela appelle un constructeur que vous avez choisi explicitement, et enveloppe les exceptions du constructeur dans InvocationTargetException pour qu'aucune ne s'échappe de manière non déclarée. Utilisez cette forme comme idiome standard, même pour le cas sans argument.

Un exemple complet : une fabrique réflexive minimale

Le programme construit des objets de trois façons : un constructeur public multi-argument, un constructeur private accessible via setAccessible, et l'idiome moderne sans argument — puis il montre un constructeur qui lève une exception enveloppée, et la signature du raccourci déprécié pour comparaison.

java— editable, runs on the server

Ce que l'exécution nous enseigne :

  • La fabrique générique build a créé Widget et Hidden à partir d'une Class et d'un tableau de types de paramètres — sans nommer aucun type dans une expression new. Cette signature, <T> T build(Class<T>, Class<?>[], Object...), ressemble essentiellement au cœur d'instanciation d'un conteneur DI : on lui passe un type et des arguments, il retourne une instance.
  • getDeclaredConstructor().newInstance() a produit le Widget par défaut, illustrant le remplacement moderne de Class.newInstance(). Préférez toujours cette forme : elle vous permet de choisir le constructeur et achemine les exceptions du constructeur via InvocationTargetException au lieu de laisser fuir des exceptions vérifiées non déclarées.
  • L'instance Hidden créée par réflexion n'était pas le même objet que Hidden.INSTANCE (same instance? false). setAccessible(true) a contourné le constructeur private et créé une deuxième instance — preuve concrète que la reflection peut briser la garantie fondamentale d'un singleton. Les singletons défensifs lèvent une exception depuis le constructeur si une instance existe déjà ; les enums sont immunisés par construction.
  • Le constructeur qui a rejeté une taille négative a levé IllegalArgumentException depuis son corps, et cela a été exposé sous forme d'InvocationTargetException avec la vraie cause à l'intérieur — même enveloppement qu'avec Method.invoke. La validation au moment de la construction est préservée à travers la reflection ; il faut juste dérouler pour la voir.
  • Constructor<T> a retourné un T typé (Widget, Hidden) sans transtypage, contrairement au Object brut de Method.invoke. Comme le constructeur est générique sur la classe qu'il construit, la fabrique reste type-safe à sa frontière même si tout à l'intérieur est réflexif.

Exercices

Pratique
Un collègue écrit 'MyService s = MyService.class.newInstance();' et le linter signale que 'newInstance()' est déprécié. Quel est le remplacement recommandé, et quelle est la principale raison pratique pour laquelle l'ancienne forme a été dépréciée ?
Un collègue écrit 'MyService s = MyService.class.newInstance();' et le linter signale que 'newInstance()' est déprécié. Quel est le remplacement recommandé, et quelle est la principale raison pratique pour laquelle l'ancienne forme a été dépréciée ?
Was this page helpful?