System.out
被声明为public static final PrintStream out
。
但您可以致电System.setOut()
重新分配。
咦?如果是final
?
(同一点适用于System.in
和System.err
)
更重要的是,如果你可以改变公共静态最终字段,那么final
给你的保证(如果有的话)意味着什么呢? (我从未意识到也没有预料到System.in/out/err表现为final
变量)
答案 0 :(得分:55)
JLS 17.5.4 Write Protected Fields:
通常,不能修改最终的静态字段。但是,
System.in
,System.out
和System.err
是最终的静态字段,由于遗留原因,必须允许通过方法System.setIn
,System.setOut
更改System.setErr
。我们将这些字段称为写保护,以区别于普通的最终字段。编译器需要以不同于其他最终字段的方式处理这些字段。例如,读取普通的最终字段对同步“免疫”:锁定或易失性读取中涉及的屏障不必影响从最终字段读取的值。由于可以看到写保护字段的值发生变化,因此同步事件应该对它们产生影响。因此,语义要求将这些字段视为用户代码无法更改的普通字段,除非该用户代码位于
System
类中。
顺便说一句,实际上,您可以通过调用final
来调用setAccessible(true)
字段(或使用Unsafe
方法)。这些技术在反序列化期间,通过Hibernate和其他框架等使用,但它们有一个限制:在修改之前看到最终字段值的代码不能保证在修改后看到新值。有关字段的特殊之处在于它们不受此限制,因为编译器会以特殊方式处理它们。
答案 1 :(得分:28)
Java使用本机方法来实现setIn()
,setOut()
和setErr()
。
在我的JDK1.6.0_20上,setOut()
看起来像这样:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
...
private static native void setOut0(PrintStream out);
您仍然无法“正常”重新分配final
个变量,即使在这种情况下,您也不会直接重新分配字段(即您仍然无法编译“System.out = myOut
”)。本机方法允许您在常规Java中无法做到的一些事情,这解释了为什么本机方法存在限制,例如要求对applet进行签名以使用本机库。
答案 2 :(得分:7)
为了延伸亚当所说的,这里是impl:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
和setOut0定义为:
private static native void setOut0(PrintStream out);
答案 3 :(得分:6)
取决于实施。最后一个可能永远不会改变,但它可能是实际输出流的代理/适配器/装饰器,setOut可以例如设置out成员实际写入的成员。但实际上它是本地设置的。
答案 4 :(得分:1)
在System类中声明为final的 out
是一个类级变量。
以下方法中的out是局部变量。
我们没有把类级别传递给这个方法
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }
上述方法的用法如下:
System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
现在数据将转移到该文件。 希望这种解释有道理。
因此,在改变最终关键字的目的时,本机方法或反思没有任何作用。
答案 5 :(得分:0)
至于如何,我们可以看一下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);
}
...
换句话说,JNI可以“欺骗”。 ; )
答案 6 :(得分:-2)
我认为setout0
正在修改局部级别变量out
,但无法修改类级别变量out
。