Pregunta java: "final" System.out, System.in y System.err?


System.out se declara como public static final PrintStream out.

Pero puedes llamar System.setOut() para reasignarlo

¿Huh? ¿Cómo es esto posible si es final?

(el mismo punto se aplica a System.in y System.err)

Y más importante aún, si puede mutar los campos finales estáticos públicos, ¿qué significa esto en cuanto a las garantías (si las hay) que final ¿te dio? (Nunca me di cuenta ni esperaba que System.in/out/err se comportara como final variables)


75
2018-05-10 14:16


origen


Respuestas:


JLS 17.5.4 Escribir campos protegidos:

Normalmente, los campos estáticos finales no se pueden modificar. sin embargo System.in, System.outy System.err son campos estáticos finales que, por razones heredadas, deben poder cambiarse con los métodos System.setIn, System.setOut y System.setErr. Nos referimos a estos campos como protegido contra escritura para distinguirlos de los campos finales ordinarios.

El compilador necesita tratar estos campos de forma diferente a otros campos finales. Por ejemplo, una lectura de un campo final ordinario es "inmune" a la sincronización: la barrera involucrada en un bloqueo o lectura volátil no tiene que afectar qué valor se lee de un campo final. Dado que el valor de los campos protegidos contra escritura puede cambiar, los eventos de sincronización deberían tener un efecto sobre ellos. Por lo tanto, la semántica dicta que estos campos se traten como campos normales que no se pueden cambiar por código de usuario, a menos que ese código de usuario esté en el System clase.

Por cierto, en realidad puedes mutar final campos a través de la reflexión llamando setAccessible(true) en ellos (o usando Unsafe métodos). Dichas técnicas se utilizan durante la deserialización, por Hibernate y otros marcos, etc., pero tienen una limitación: no se garantiza que el código que haya visto el valor del campo final antes de la modificación vea el nuevo valor después de la modificación. Lo especial de los campos en cuestión es que no tienen esta limitación, ya que el compilador los trata de manera especial.


55
2018-05-10 14:28



Java usa un método nativo para implementar setIn(), setOut() y setErr().

En mi JDK1.6.0_20, setOut() Se ve como esto:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

...

private static native void setOut0(PrintStream out);

Aún no puede reasignar "normalmente" final variables, e incluso en este caso, no está reasignando directamente el campo (es decir, todavía no puede compilar "System.out = myOut"). Los métodos nativos permiten algunas cosas que simplemente no se pueden hacer en Java normal, lo que explica por qué existen restricciones con los métodos nativos, como el requisito de que se debe firmar un applet para usar bibliotecas nativas.


28
2018-05-10 14:18



Para extender lo que Adam dijo, aquí está la impl:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

y setOut0 se define como:

private static native void setOut0(PrintStream out);

7
2018-05-10 14:21



Depende de la implementación. El último nunca puede cambiar, pero podría ser un proxy / adaptador / decorador para el flujo de salida real, setOut podría, por ejemplo, establecer un miembro en el que el miembro salga realmente escribe. En la práctica, sin embargo, se establece de forma nativa.


6
2018-05-10 14:22



el out que se declara como final en la clase del sistema es una variable de nivel de clase. donde como lo que está en el método de abajo es una variable local. no estamos pasando el nivel de clase, que es el último en este método

public static void setOut(PrintStream out) {
  checkIO();
  setOut0(out);
    }

el uso del método anterior es el siguiente:

System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));

ahora los datos se desviarán al archivo. Espero que esta explicación tenga sentido.

Así que no hay papel de los métodos nativos o reflexiones aquí en el cambio de propósito de la palabra clave final.


1
2017-12-13 16:48



En cuanto a cómo, podemos echarle un vistazo al código fuente para java/lang/System.c:

/*
 * The following three functions implement setter methods for
 * java.lang.System.{in, out, err}. They are natively implemented
 * because they violate the semantics of the language (i.e. set final
 * variable).
 */
JNIEXPORT void JNICALL
Java_java_lang_System_setOut0(JNIEnv *env, jclass cla, jobject stream)
{
    jfieldID fid =
        (*env)->GetStaticFieldID(env,cla,"out","Ljava/io/PrintStream;");
    if (fid == 0)
        return;
    (*env)->SetStaticObjectField(env,cla,fid,stream);
}

...

En otras palabras, JNI puede "engañar". ; )


0
2018-05-05 19:20