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é :
-
public final static PrintStream out = nullPrintStream();
-
private static PrintStream nullPrintStream() throws NullPointerException
-
{
-
if (currentTimeMillis() > 0)
-
{ return null;
-
}
-
throw new NullPointerException();
-
}
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éthodeinitializeSystemClass()
. - La présence d’un setter :
-
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
:
-
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 !
Ah, voilà un article comme je les aime : inattendu et instructif. Encore merci.