2011-05-10 13 views
73

System.out se declara como public static final PrintStream out.java: "final" System.out, System.in y System.err?

Pero puede llamar al System.setOut() para reasignarlo.

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

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

Y lo más importante, si se puede mutar los campos public static final, ¿qué significa esto en cuanto a las garantías (si los hay) que final le da? (Nunca me di cuenta ni esperaba System.in/out/err comportó como variables de final)

+3

Los campos finales no disfrutan de muchos beneficios por parte de la propia JVM, aunque el verificador los controla estrictamente. También hay formas de modificar los campos finales pero no a través del código java estándar (ya que es un tema del verificador). Se realiza a través de Inseguro y expuesto en java a través de Field.set (se necesita acceso verdadero), que se compila con lo mencionado inseguro. También JNI puede hacerlo, por lo tanto, la JVM no está tan interesada en intentar optimizar ... {quizás debería haber estructurado el comentario como una respuesta, pero meh} – bestsss

Respuesta

54

JLS 17.5.4 Write Protected Fields:

Normalmente, los campos estáticos finales no pueden ser modificados. Sin embargo, System.in, System.out y System.err son campos estáticos finales que, por razones heredadas, se deben permitir cambiar por los métodos System.setIn, System.setOut y System.setErr. Nos referimos a estos campos como protegidos 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 la clase System.

Por cierto, en realidad se puede mutar final campos a través de la reflexión llamando setAccessible(true) en ellos (o mediante el uso de métodos Unsafe). 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.

+4

aha ............ –

+4

¡Que el FSM bendiga el código heredado por la adorable manera en que compromete el diseño futuro! – ArtB

+1

>> Esta técnica se usa durante la deserialización << esto no es cierto ahora, desereliazation usa Inseguro (más rápido) – bestsss

28

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

En mi JDK1.6.0_20, setOut() se parece a esto:

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

... 

private static native void setOut0(PrintStream out); 

Todavía no se puede "normalmente" reasignar final las variables, e incluso en este caso, no está reasignando directamente el campo (es decir, usted 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.

+0

AAAh me lo ganaste :) –

+1

OK, entonces es un puerta de atrás en torno a la semántica pura de Java ... ¿podría responder la parte de la pregunta que agregué, es decir, si puede reasignar las secuencias, ¿'final' realmente tiene algún significado aquí? –

+1

Probablemente sea definitivo, por lo que uno no puede hacer algo como System.out = new SomeOtherImp(). Pero aún puedes usar los setters usando el enfoque nativo como lo ves arriba. –

7

Extender en lo que Adam dijo, aquí es la impl:

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

y setOut0 se define como:

private static native void setOut0(PrintStream out); 
6

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.

1

out que se declara como final en la clase del sistema es una variable de nivel de clase. donde como out que está en el siguiente método es una variable local. estamos pasando ningún donde el nivel de clase a cabo que es en realidad una final en este método

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

uso del método anterior es tal como a continuación:

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

ahora los datos se ser desviado al archivo. espero que esta explicación tenga sentido.

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

+1

setOut0 está modificando la variable de clase, que es definitiva. – fgb

0

En cuanto a la forma, podemos echar un vistazo al código fuente a 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, puede JNI "trampa". ;)