System.out
声明为public static final PrintStream out
。
但是您可以致电System.setOut()
重新分配它。
??如果是这样怎么可能final
?
(System.in
和适用于System.err
)
更重要的是,如果您可以对public static final字段进行突变,那么就可以为final
您提供的保证(如果有)意味着什么?(我从未意识到,也没想到System.in/out/err表现为final
变量)
System.out
声明为public static final PrintStream out
。
但是您可以致电System.setOut()
重新分配它。
??如果是这样怎么可能final
?
(System.in
和适用于System.err
)
更重要的是,如果您可以对public static final字段进行突变,那么就可以为final
您提供的保证(如果有)意味着什么?(我从未意识到,也没想到System.in/out/err表现为final
变量)
Answers:
通常,最终的静态字段可能不会被修改。然而
System.in
,System.out
和System.err
是,由于遗留原因,必须允许通过方法来改变最终的静态字段System.setIn
,System.setOut
和System.setErr
。我们称这些字段为写保护的,以区别于普通的最终字段。编译器需要将这些字段与其他最终字段区别对待。例如,读取普通的最终字段对同步是“免疫”的:锁定或易失性读取中涉及的屏障不必影响从最终字段读取的值。由于可以看到写保护字段的值发生了变化,因此同步事件应该对其产生影响。因此,语义要求将这些字段视为不能由用户代码更改的普通字段,除非该用户代码在
System
类中。
顺便说一句,实际上,您可以final
通过调用setAccessible(true)
(或使用Unsafe
方法)通过反射来使字段发生变化。Hibernate和其他框架等在反序列化过程中使用了此类技术,但它们有一个局限性:修改前已看到final字段值的代码不能保证在修改后会看到新值。有问题的字段的特殊之处在于它们不受此限制,因为编译器以特殊方式对待它们。
setAccessible(true)
仅适用于非static
字段,这使其适合于帮助反序列化或克隆代码的任务,但是无法更改static final
字段。这就是为什么引用的文本以“通常,不得修改最终静态字段”开头的原因,指的final static
是这些字段的性质和三个例外。实例字段的情况在另一个地方讨论。
final
摘除修饰符。似乎比所有这些“写保护”的东西都简单。我很确定这不是一个重大变化。
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签名才能使用本机库。
final
这里实际上有什么意义吗?
继续亚当所说的,这是暗示:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
setOut0定义为:
private static native void setOut0(PrintStream out);
在out
其被声明为最终在系统类是一类电平变量中。下面的方法中的哪一个是局部变量。我们没有地方将类级别传递出去,这实际上是该方法的最后一个级别
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);
}
...
换句话说,JNI可以“作弊”。; )