mar.
2010
Java Quiz #36
Pour créer des instances de classes dont on ne connait que le nom, rien de plus facile : il suffit de récupérer l'instance de Class<?>
puis d'appeler newInstance()
dessus, et puis de traiter les nombreuses exceptions susceptibles d'être levées.
C'est ce que fait la méthode statique newInstance
ci-dessous :
public class TestNewInstance { public static void main(String[] args) { final Object o = newInstance(args[0]); System.out.println(String.valueOf(o)); } private static Object newInstance(String className) { try { Class<?> clazz = Class.forName(className); return clazz.newInstance(); } catch (ClassNotFoundException e) { System.out.println("-- Class Not Found --"); } catch (IllegalAccessException e) { System.out.println("-- Illegal Access --"); } catch (InstantiationException e) { System.out.println("-- Cannot Instantiate --"); } return null; } }
Bien sûr, la classe en question doit posséder un constructeur sans argument ; si ce n'est pas le cas, InstantiationException
sera levée.
Cependant, malgré toutes ces précautions, ce code pose un gros problème de robustesse, qui ne se révèle que pour quelques classes bien particulières. Pourquoi ? Comment l'éliminer ?
La réponse à cette question est donnée par la Javadoc de Class.newInstance()
(traduction de votre serviteur) :
Notez que cette méthode propage toute exception lancée par le constructeur sans argument, y compris une exception vérifiée [une instance de la classe
Exception
qui n'est pas une instance deRuntimeException
, NdT]. En réalité, utiliser cette méthode court-circuite le test d'existence d'une exception qui, normalement, est fait par le compilateur. La méthodeConstructor.newInstance()
évite ce problème en encapsulant toute exception lancée par le constructeur par uneInvocationTargetException
(qui est une exception vérifiée).
Le problème de robustesse vient donc des constructeurs qui lancent des exceptions non-Runtime. Et la JavaDoc indique également comment éviter ce problème (cf. morceau de code donné ci-dessous dans le commentaire n°2 de bgiraudou, qui a parfaitement décrit le problème.)
Pour être complet et répondre aux questions des commentaires n°3 et n°4 : le cas des constructeurs qui lancent des exceptions est le principal problème de robustesse du code de ce quiz :
- Lorsque le constructeur de la classe à instancier est privé à la classe,
Class.newInstance()
lèveIllegalAccessException
. - Lorsque la classe à instancier est une classe interne non statique (donc liée à une instance de classe englobante),
Class.newInstance()
lèveInstantiationException
.
Ces deux exceptions font partie des exceptions qu'il faut traiter en appelant Class.newInstance()
. Ces deux cas ne posent donc pas de problème de robustesse particulier.
Quant au point soulevé par le commentaire n°1 (pas de vérification de l'accessibilité de la classe à charger), c'est effectivement le second problème de robustesse de ce code, même s'il est plus rarement rencontré que le premier, car il est assez peu fréquent de définir explicitement un gestionnaire de sécurité système : contrairement à forName(String name, boolean initialize, ClassLoader loader)
, Class.forName(String name)
ne vérifie pas auprès du SecurityManager
système que la classe peut être chargée (dans le cas où un tel gestionnaire de sécurité système a bien été défini). Il suffit de jeter un coup d'œil au code source de cette méthode pour constater la différence d'implémentation entre les deux méthodes. A noter cependant que Class.newInstance()
, elle, fait bien une vérification auprès du gestionnaire de sécurité système, mais pas la même (en l'occurrence, elle vérifie qu'un membre public de la classe peut être accédé par la classe appelante, ce qui n'est pas le même droit que celui que Class.forName
doit vérifier).
Commentaires
Le méthode Class.forName(String name) ne vérifie pas l'accessibilité de la classe pour l'appelant ce qui pourrait avoir des conséquences graves.
Il faut définir un SecurityManager pour vérifier les accès.
Si le constructeur de la classe à instancier renvoie une exception vérifiée, la méthode ne l'attrapera pas et la propagera à l'appelant (ici la méthode main) du coup l'exécution plantera. Pour éviter cela il vaut mieux utiliser la méthode @@newInstance@@ de la classe @@Constructor@@ :
///[java]
private static Object newInstance2(String className) {
///
Quid des singletons avec leur constructeur private ?
J'aurais tendance à dire que les inner classes non statiques (donc dépendantes d'une instance de classe mère) poseront aussi un soucis.
Il est tout a fait possible d'instancier une classe qui n'a pas de constructeur public ;)