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

Prochaine sessions inter-entreprises : 13-16 février 2018
Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Java Quiz #16

Qu'affiche ce code ?

  1. class Super {
  2. static { System.out.print("**Super** "); }
  3. }
  4.  
  5. class Un {
  6. static { System.out.print("**Un** "); }
  7. }
  8.  
  9. class Deux extends Super {
  10. static { System.out.print("**Deux** "); }
  11.  
  12. static final String DEUX = "dEuX";
  13. }
  14.  
  15. class TestClasse {
  16. public static void main(String[] args) throws Exception {
  17. Un o = null;
  18. Class<Deux> c = Deux.class;
  19.  
  20. System.out.println(Deux.DEUX);
  21. System.out.println(((Object) o == (Object) c));
  22. System.out.println(c.equals(o));
  23. System.out.println(c); // Appelle toString() sur c.
  24. System.out.println(c.newInstance());
  25. }
  26. }

Réponse (avec la JVM Sun 1.5, ou la JVM IBM de même niveau) :

dEuX
false
false
class Deux
**Super** **Deux** Deux@82ba41

L'analyse de ce résultat montre que les blocs d'initialisation statique de Super et Deux ne sont exécutés que lorsqu'une instance de Deux est créée, et pas avant. Quant au bloc d'initialisation statique de Un, il n'est tout simplement jamais exécuté. Étonnant, non ?

Conclusion : utiliser un littéral classe (ici Deux.class) ne provoque pas le chargement de la classe associée. Et déclarer une variable du type de cette classe non plus. Plus fort : manipuler l'instance de Class représentant cette classe, et appeler des opérations sur cette instance (y compris les opérations d'introspection), ne provoquent toujours pas le chargement de la classe.

En fait, comme l'indique clairement la spécification Java (JLS), seules l'une des 5 situations suivantes garantissent le chargement d'une classe T, si celle-ci ne l'est pas encore :

  • une instance de T est créée.
  • une méthode statique de T est invoquée.
  • une valeur est affectée à un attribut statique de T.
  • la valeur d'un attribut statique non constant de T est utilisée (cf. JLS 4.12.4).
  • T est une classe de niveau fichier, et une instruction assert (cf. JLS 14.10), situé à l'intérieur de son code, est exécutée.

Aucun autre cas ne le garantit.

Notez que ce comportement "paresseux" a été introduit avec la JVM 1.5. Auparavant, certaines opérations, comme la déclaration d'une référence sur une classe provoquait son chargement. Ainsi, le code du quiz donne le résultat suivant avec la JVM Sun 1.4.2 (il faut bien sûr apporter une toute petite modification au code du quiz, que je vous laisse trouver) :

**Super** **Deux** dEuX
false
false
class Deux
Deux@1ac04e8

Cette petite modification de comportement peut être la cause de bugs assez subtils lors de la migration vers une JVM 1.5+.
Par exemple, nombre de développeurs ont eu besoin des énumérations avant l'introduction des Enum dans Java5, et ont donc utilisé les Enums de la librairie commons.lang. Or, cette implémentation repose sur des Map<Class, Entry> ; les constantes énumérées ne sont donc référencées qu'indirectement, et leurs classes ne sont pas chargées sur les JVM 1.5+. Résultat : la comparaison de ces Enums renvoie toujours false...
Il n'y a malheureusement aucune solution "propre" à ce problème, à part migrer vers les Enumss natifs de Java 5+.


Commentaires

1. Le lundi 29 septembre 2008, 10:52 par Bh@Mp0
dEuX => affichage d'une constante
false => o est null
false => o est null
class Deux => toString de base
**Super** **Deux** Deux => on remonte jusqu'au père avant de redescendre + pas sûr pour le "Deux" final, peut-être est-ce "Deux@xxxxx" avec la réf de l'instance créée
2. Le lundi 29 septembre 2008, 10:53 par Bh@Mp0

PS : l'affichage du commentaire est loupé, il manque les retours chariot ...

3. Le lundi 6 octobre 2008, 10:51 par Histrion

C'est donc cela qu'on appelle le Just In Time ? :)

4. Le lundi 6 octobre 2008, 14:34 par Olivier Croisier

Hmmm non, la compilation JIT permet de recompiler des portions de code au runtime, notamment afin d'optimiser certains chemins d'exécution. Voir sur Wikipedia

Ajouter un commentaire

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