En la fuente System.java, ¿los flujos de entrada, salida y error estándar se declaran definitivos y nulos inicializados?

public final static InputStream in = null; public final static PrintStream out = null; public final static PrintStream err = null; 

Pero como sabemos muy bien, estas secuencias están conectadas a la consola de forma predeterminada y ya están abiertas. También hay métodos en la clase del sistema setIn (), setOut y setErr () para redirigir las secuencias. ¿Cómo es esto posible cuando se han declarado definitivos y se han establecido en el valor de inicialización nulo?

Recopilé el siguiente código, establecí un punto de interrupción en la llamada a println () y depuré usando netbeans. Mi objective era determinar exactamente cuándo se inicializa la variable System.in a la salida estándar al ingresar a la fuente. Pero parece que el flujo de salida ya está inicializado cuando se llama al método main.

 public static void main(String[] args) { System.out.println("foo"); } 

Más tarde se establecen mediante los métodos nativos SetIn0 , SetOut0 y SetErr0

 private static native void setIn0(InputStream in); private static native void setOut0(PrintStream out); private static native void setErr0(PrintStream err); 

se llama desde el método initializeSystemClass , que según JavaDoc se llama después de la inicialización del hilo .

 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)); 

Esto se hace para evitar “piratería”. Estos campos solo pueden ser cambiados por instaladores apropiados que invocan métodos native

 private static native void setIn0(InputStream in); private static native void setOut0(PrintStream out); private static native void setErr0(PrintStream err); 

Los métodos nativos pueden hacer todo, incluso cambiar los campos finales.

final campos final no son necesariamente constantes. Aún se pueden manipular, solo se evita la manipulación en tiempo de comstackción, específicamente evitando que uses el operador de asignación ( = ). Ver esta pregunta y JLS §17.5.3 , específicamente:

final campos final se pueden cambiar mediante reflexión y otros medios dependientes de la implementación.

Esto es necesario para cosas como la deserialización. Esto también puede causar algunas advertencias interesantes ya que los comstackdores pueden optimizar los campos final en tiempo de comstackción y tiempo de ejecución. El JLS vinculado anteriormente tiene un ejemplo de esto.