Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

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 :

  1. public class TestNewInstance {
  2. public static void main(String[] args) {
  3. final Object o = newInstance(args[0]);
  4. System.out.println(String.valueOf(o));
  5. }
  6.  
  7. private static Object newInstance(String className) {
  8. try {
  9. Class<?> clazz = Class.forName(className);
  10.  
  11. return clazz.newInstance();
  12. } catch (ClassNotFoundException e) {
  13. System.out.println("-- Class Not Found --");
  14. } catch (IllegalAccessException e) {
  15. System.out.println("-- Illegal Access --");
  16. } catch (InstantiationException e) {
  17. System.out.println("-- Cannot Instantiate --");
  18. }
  19. return null;
  20. }
  21. }

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 de RuntimeException, 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éthode Constructor.newInstance() évite ce problème en encapsulant toute exception lancée par le constructeur par une InvocationTargetException (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ève IllegalAccessException.
  • Lorsque la classe à instancier est une classe interne non statique (donc liée à une instance de classe englobante), Class.newInstance() lève InstantiationException.

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

1. Le lundi 22 mars 2010, 12:34 par Julien Guiraud

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.

2. Le lundi 22 mars 2010, 13:05 par bgiraudou

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) {

       try {
Class<?>clazz = Class.forName(className);
Constructor<?> constructor = clazz.getConstructor(null);
return constructor.newInstance(null);
} catch (ClassNotFoundException e) {
System.out.println("-- Class Not Found --");
}
catch (NoSuchMethodException e) {
System.out.println("-- No Null Constructor --");
} catch (InstantiationException e) {
System.out.println("-- Cannot Instantiate --");
} catch (IllegalAccessException e) {
System.out.println("-- Illegal Access --");
} catch (InvocationTargetException e) {
System.out.println("-- Checked Exception --");
}
return null;
}

///

3. Le mardi 23 mars 2010, 06:28 par dclairac

Quid des singletons avec leur constructeur private ?

4. Le mercredi 24 mars 2010, 11:39 par Hikage

J'aurais tendance à dire que les inner classes non statiques (donc dépendantes d'une instance de classe mère) poseront aussi un soucis.

5. Le jeudi 8 avril 2010, 07:19 par Icy

Il est tout a fait possible d'instancier une classe qui n'a pas de constructeur public ;)

           De la meme facon qu'on peut lancer des methodes private ou acceder a des field private... merci la reflection.
           
           Constructor<NoPublicConst>[] cons = NoPublicConst.class.getDeclaredConstructors();         
           cons[0].setAccessible(true); 
           NoPublicConst instance = (NoPublicConst)cons[0].newInstance(null);

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.