In the System.java source, the standard input, output and error streams are declared final and initialized null?
This is done in order to prevent "hacking". These fields can be changed only by appropriate setters that call native
methods
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
Native methods can do everything including changing final fields.
They are later on set by native methods SetIn0
, SetOut0
and SetErr0
private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);
called from the initializeSystemClass
method, which according to the JavaDoc is called after thread initialization.
FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true));
final
fields are not necessarily constant. They can still be manipulated, it's just that manipulation is only prevented at compile-time, specifically by preventing you from using the assignment operator (=
). See this question and JLS §17.5.3, specifically:
final
fields can be changed via reflection and other implementation-dependent means.
This is necessary for things like deserialization. This can also cause some interesting caveats since compilers can optimize final
fields on compile-time and run-time. The JLS linked above has an example of this.