System.out : plus complexe qu’il n’y paraît

En analysant la classe java.lang.System du JDK 6.0, on peut tomber sur du code pour le moins surprenant. Vous croyiez connaître System.out ? Ce champ à l’allure anodine cache en réalité quelques petites bizarreries amusantes…

Une initialisation tarabiscotée

La première surprise vient de la façon dont ce champ est initialisé :

  1. public final static PrintStream out = nullPrintStream();
  2. private static PrintStream nullPrintStream() throws NullPointerException
  3. {
  4. if (currentTimeMillis() > 0)
  5. { return null;
  6. }
  7. throw new NullPointerException();
  8. }

Mais pourquoi tant de circonvolutions pour simplement assigner null au champ ?
La javadoc de la méthode nullPrintStream() apporte un élément de réponse :

The following two methods exist because in, out, and err must be initialized to null. The compiler, however, cannot be permitted to inline access to them, since they are later set to more sensible values by initializeSystemClass().

Traduction : le compilateur est bien sympa de vouloir optimiser le code en remplaçant les les accès au champ (coûteux) directement par sa valeur, mais dans ce cas précis, on s’en passerait bien, car la valeur nulle n’est que temporaire et sera modifiée par la suite.
Ceci explique d’ailleurs pourquoi la méthode nullPrintStream() ne renvoie pas directement null : le compilateur n’est pas stupide et s’en apercevrait ; le test sur la date courante permet donc de le mystifier en introduisant un élément d’incertitude, non déterminable à la compilation.
Question bonus : la JVM se lance-t-elle sur un système dont la date est réglée avant Epoch, la date de référence ?

Final… ou pas ?

Et là, une question troublante se pose : puisque System.out est final et qu’on lui assigne null, comment se fait-il que l’on n’obtienne pas un NullPointerException lorsqu’on appelle, par exemple, System.out.println() ?
Quelques indices nous mettent la puce à l’oreille :

  • La Javadoc vue plus haut indique qu’une nouvelle valeur sera réassignée à System.out par la méthode initializeSystemClass().
  • La présence d’un setter :
  1. public static void setOut(PrintStream out)

On nous aurait donc menti ? Serait-il possible de changer la valeur d’un champ final ?
En fait, pour vous et moi, non. Mais la classe java.lang.System dispose de certains privilèges, comme indiqué dans la JLS, §17.5.4 :

Normally, final static fields may not be modified. However System.in, System.out, and System.err are final static fields that, for legacy reasons, must be allowed to be changed by the methods System.setIn, System.setOut and System.setErr. (…) Therefore, the semantics dictate that these fields be treated as normal fields that cannot be changed by user code, unless that user code is in the System class.

En y regardant de plus près, l’interaction avec la console est apparemment une fonction très bas niveau, car le setter public fait en réalité appel à une méthode native :

  1. private static native void setOut0(PrintStream out);

En conclusion, il est parfois très instructif de fouiller dans les classes du JDK, on peut y découvrir des choses intéressantes !

Une réflexion sur “System.out : plus complexe qu’il n’y paraît

Laisser un commentaire

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.

%d blogueurs aiment cette page :