java:“final”System.out,System.in和System.err?

时间:2011-05-10 14:16:49

标签: java final

System.out被声明为public static final PrintStream out

但您可以致电System.setOut()重新分配。

咦?如果是final

,这怎么可能呢?

(同一点适用于System.inSystem.err

更重要的是,如果你可以改变公共静态最终字段,那么final给你的保证(如果有的话)意味着什么呢? (我从未意识到也没有预料到System.in/out/err表现为final变量)

7 个答案:

答案 0 :(得分:55)

JLS 17.5.4 Write Protected Fields

  

通常,不能修改最终的静态字段。但是,System.inSystem.outSystem.err是最终的静态字段,由于遗留原因,必须允许通过方法System.setInSystem.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