Java 爪哇语:;“最后的”;System.out、System.in和System.err?
声明为Java 爪哇语:;“最后的”;System.out、System.in和System.err?,java,final,Java,Final,声明为公共静态最终打印流输出 但你可以打电话重新分配 嗯?如果是最终版,这怎么可能呢 (这一点同样适用于System.in和System.err) 更重要的是,如果您可以修改公共静态final字段,那么final提供的保证(如果有)意味着什么?(我从未意识到也从未期望System.in/out/err作为final变量)Java使用本机方法实现setIn()、setOut()和setErr() 在我的JDK1.6.020上,setOut()如下所示: public static void set
公共静态最终打印流输出
但你可以打电话重新分配
嗯?如果是最终版
,这怎么可能呢
(这一点同样适用于System.in
和System.err
)
更重要的是,如果您可以修改公共静态final字段,那么
final
提供的保证(如果有)意味着什么?(我从未意识到也从未期望System.in/out/err作为final
变量)Java使用本机方法实现setIn()
、setOut()
和setErr()
在我的JDK1.6.020上,setOut()
如下所示:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
...
private static native void setOut0(PrintStream out);
您仍然无法“正常”重新分配
final
变量,即使在这种情况下,您也不能直接重新分配字段(即,您仍然无法编译“System.out=myOut
”)。本机方法允许一些您在常规Java中无法完成的事情,这解释了为什么本机方法有一些限制,例如要求对小程序进行签名才能使用本机库。要扩展Adam所说的内容,以下是impl:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
而setOut0定义为:
private static native void setOut0(PrintStream out);
取决于实施情况。最后一个可能永远不会更改,但它可以是实际输出流的代理/适配器/装饰器,例如,setOut可以设置out成员实际写入的成员。但实际上,它是本机设置的。: 通常情况下,最终静态字段可能不会被修改。但是
System.in
、System.out
和System.err
是最终的静态字段,出于遗留原因,必须允许通过System.setIn
、System.setOut
和System.setErr
方法更改这些字段。我们将这些字段称为写保护字段,以区别于普通的最终字段
编译器需要将这些字段与其他最终字段区别对待。例如,对普通最终字段的读取对同步“免疫”:锁或易失性读取中涉及的屏障不必影响从最终字段读取的值。由于写保护字段的值可能会发生变化,因此同步事件应该会对其产生影响。因此,语义要求将这些字段视为不能由用户代码更改的正常字段,除非该用户代码位于系统
类中
顺便说一句,实际上,您可以通过对
setAccessible(true)
字段进行调用(或使用Unsafe
方法),通过反射来变异final
字段。Hibernate和其他框架等在反序列化过程中使用了这些技术,但它们有一个限制:在修改之前看到final字段值的代码不能保证在修改之后看到新值。有问题的字段的特殊之处在于,它们不受此限制,因为编译器以特殊方式处理它们。在系统类中声明为final的out
是类级变量。
其中,下面方法中的as 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 (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可以“欺骗”;) 我认为
setout0
正在修改本地级变量out
,它不能修改类级变量out
好的,所以它是纯Java语义的后门。。。你能回答我添加的问题的一部分吗,即如果你能重新分配流,final
在这里真的有什么意义吗?它可能是final,所以你不能做类似System.out=new SomeOtherImp()的事情。但是您仍然可以使用本机方法使用setter,正如您上面看到的。我猜在这种情况下,对本机setIn0和setOut0方法的调用将真正修改最终变量的值,本机方法可能可以做到这一点。。。这就像在游戏中使用作弊代码:S@Danilo,是的,它确实修改:)@Jason,无法直接设置它需要在调用setIn/setErr时进行安全检查。一切都很公平。java确实修改最终字段的示例:java.util.Random(字段种子)可能FSM会以一种巧妙的方式为遗留代码祝福,因为它会损害未来的设计!>>此技术在反序列化过程中使用。setAccessible(true)
仅适用于非静态
字段,这使得它适用于帮助反序列化或克隆代码的任务,但无法更改静态最终
字段。这就是为什么引用的文本以“通常情况下,最终静态字段不能修改”开头,指的是这些字段的最终静态
性质和三个例外。实例字段的情况在另一个地方讨论。我想知道为什么它们不简单地去掉final
修饰符;似乎比所有这些“写保护”的东西都简单。我很确定这不是一个突破性的改变。@Holger有一种方法可以改变静态的final字段(直到Java8)。公共类OnePrinter{private static final Integer ONE=1;公共静态void printOne(){System.out.println(ONE);}}},然后您可以使用字段z=OnePrinter.class.getDeclaredField(“ONE”);z、 setAccessible(true);字段f=Field.class.getDeclaredField(“修饰符”);int modifiers=z.getModifiers();f、 setAccessible(true);f、 集合(z,修饰符和~Modifier.FINAL);z、 set(null,2);OnePrinter.prin
/*
* 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);
}
...