java: "final" System.out, System.in and System.err?
Solution 1:
JLS 17.5.4 Write Protected Fields:
Normally, final static fields may not be modified. However
System.in
,System.out
, andSystem.err
are final static fields that, for legacy reasons, must be allowed to be changed by the methodsSystem.setIn
,System.setOut
andSystem.setErr
. We refer to these fields as being write-protected to distinguish them from ordinary final fields.The compiler needs to treat these fields differently from other final fields. For example, a read of an ordinary final field is "immune" to synchronization: the barrier involved in a lock or volatile read does not have to affect what value is read from a final field. Since the value of write-protected fields may be seen to change, synchronization events should have an effect on them. 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.
By the way, actually you can mutate final
fields via reflection by calling setAccessible(true)
on them (or by using Unsafe
methods). Such techniques are used during deserialization, by Hibernate and other frameworks, etc, but they have one limitation: code that have seen value of final field before modification is not guaranteed to see the new value after modification. What's special about the fields in question is that they are free of this limitation since they are treated in special way by the compiler.
Solution 2:
Java uses a native method to implement setIn()
, setOut()
and setErr()
.
On my JDK1.6.0_20, setOut()
looks like this:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
...
private static native void setOut0(PrintStream out);
You still can't "normally" reassign final
variables, and even in this case, you aren't directly reassigning the field (i.e. you still can't compile "System.out = myOut
"). Native methods allow some things that you simply can't do in regular Java, which explains why there are restrictions with native methods such as the requirement that an applet be signed in order to use native libraries.
Solution 3:
To extend on what Adam said, here is the impl:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
and setOut0 is defined as:
private static native void setOut0(PrintStream out);
Solution 4:
Depends on the implementation. The final one may never change but it could be a proxy/adapter/decorator for the actual output stream, setOut could for example set a member that the out member actually writes to. In practice however it is set natively.