java:“final”System.out,System.in和System.err?
System.out
被声明为public static final PrintStream out
。
但是你可以调用System.setOut()
来重新分配它。
咦? 如果这是final
这怎么可能?
(同样的观点适用于System.in
和System.err
)
更重要的是,如果你可以改变公共静态final域,这就意味着final
给你的保证(如果有的话)是什么意思? (我从来没有意识到也没有料到System.in/out/errperformance为final
variables)
JLS 17.5.4写保护字段 :
通常情况下,最终的静态字段可能不会被修改。 但
System.in
,System.out
和System.err
是最终的静态字段,由于传统的原因,必须允许通过System.setIn
,System.setOut
和System.setErr
方法更改。 我们将这些字段称为写保护区别于普通的最终字段。编译器需要将这些字段与其他最终字段区分开来。 例如,对一个普通的final字段的读取对同步是“免疫的”:locking或易失性读取中涉及的屏障不必影响从最终字段读取什么值。 由于写保护字段的值可能会发生变化,所以同步事件应该对它们产生影响。 因此,语义规定这些字段被视为不能被用户代码改变的正常字段,除非该用户代码在
System
类中。
顺便说一下,实际上你可以通过调用setAccessible(true)
(或者使用Unsafe
方法)通过reflection来改变final
字段。 这样的技术在反序列化,Hibernate和其他框架等中被使用,但是它们有一个限制:在修改之前已经看到最终字段值的代码不能保证在修改之后看到新的值。 问题领域的特殊之处在于它们没有这个限制,因为它们被编译器以特殊的方式处理。
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
variables,即使在这种情况下,你也不能直接重新分配字段(即你仍然不能编译“ System.out = myOut
”)。 本地方法允许您在常规Java中无法完成的某些操作,这就解释了为什么本地方法受到限制,例如为了使用本机库而需要对applet进行签名。
延续亚当说的话,下面是这个impl:
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }
setOut0被定义为:
private static native void setOut0(PrintStream out);
取决于实施。 最后一个可能永远不会改变,但它可能是实际输出stream的代理/适配器/装饰器,setOut可以例如设置out成员实际写入的成员。 然而在实践中,它是在本地设置的。
在System类中声明为final的out
是一个类的variables。 在哪里下面哪个方法是局部variables。 我们不是在这个方法中通过实际上是最后一个级别的级别
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }
以上方法的用法如下:
System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
现在数据将被转移到文件。 希望这个解释是有道理的。
所以在这里没有本地方法的作用或者反思最终关键字的目的。
至于如何,我们可以看一下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 (ie 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可以“欺骗”。 ; )