System.out
被声明为 public static final PrintStream out
。
但是你可以调用System.setOut()
来重新分配它。
如果它是 final
,这怎么可能呢?
(同样适用于 System.in
和 System.err
)
更重要的是,如果你可以改变公共静态 final 字段,那么在关于 final
给你的保证方面会有什么含义呢? (我从没意识到 System.in/out/err 表现得像是 final
变量)
System.out
被声明为 public static final PrintStream out
。
但是你可以调用System.setOut()
来重新分配它。
如果它是 final
,这怎么可能呢?
(同样适用于 System.in
和 System.err
)
更重要的是,如果你可以改变公共静态 final 字段,那么在关于 final
给你的保证方面会有什么含义呢? (我从没意识到 System.in/out/err 表现得像是 final
变量)
通常,final static字段不能被修改。但是,由于历史原因,
System.in
、System.out
和System.err
是final static字段,必须允许通过System.setIn
、System.setOut
和System.setErr
方法进行更改。我们将这些字段称为“写保护”,以区别于普通的final字段。编译器需要将这些字段与其他final字段区别对待。例如,读取普通final字段对同步是“免疫”的:锁定或volatile读取涉及的障碍不必影响从final字段读取的值。由于可能看到写保护字段的值发生变化,同步事件应对它们产生影响。因此,语义规定这些字段被视为无法被用户代码更改的普通字段,除非该用户代码在
System
类中。
顺便说一下,实际上你可以通过反射调用setAccessible(true)
(或使用Unsafe
方法)来改变final
字段。这些技术在反序列化、Hibernate和其他框架等过程中使用,但它们有一个限制:在修改前看到final字段的值的代码不一定会在修改后看到新的值。这些特定字段的特殊之处在于它们被编译器以特殊方式处理,没有这个限制。
setAccessible(true)
只适用于非静态字段,这使得它适用于帮助反序列化或克隆代码的任务,但是没有办法更改静态常量。这就是为什么引用文本以“通常情况下,不能修改final static字段”开头,指的是这些字段的final static特性和三个例外情况。实例字段的情况在另一个地方进行了讨论。 - HolgerSystem.out().println
。 - Mark VYJava使用本地方法来实现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中无法实现的操作,这解释了为什么有关本地方法的限制,例如要求小程序签名才能使用本地库。
final
在这里实际上是否有任何意义? - Jason Spublic static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
setOut0被定义为:
private static native void setOut0(PrintStream out);
这取决于具体实现。最终的输出流可能永远不会改变,但它可以是实际输出流的代理/适配器/装饰器。例如,setOut可以设置一个成员变量,out成员实际上写入该变量。然而,在实践中,它通常是以本地方式设置。
System
类中声明为final的out
是一个类级别变量。
而下面方法中的out
是一个局部变量。
我们没有将类级别的out
(实际上是final的)传递到这个方法中。
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
以上方法的使用如下:
System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
现在数据将被重定向到文件中。希望这个解释有意义。
因此,在更改final关键字的用途方面,原生方法或反射没有作用。
关于如何实现,我们可以查看源代码中的 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);
}
...
我认为setout0
正在修改局部变量out
,它无法修改类级别的变量out
。