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

时间:2023-02-15 17:13:20

System.out is declared as public static final PrintStream out.

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

But you can call System.setOut() to reassign it.

但是您可以调用System.setOut()来重新分配它。

Huh? How is this possible if it's final?

咦?如果它是最终的,这怎么可能?

(same point applies to System.in and System.err)

(同一点适用于System.in和System.err)

And more importantly, if you can mutate the public static final fields, what does this mean as far as the guarantees (if any) that final gives you? (I never realized nor expected System.in/out/err behaved as final variables)

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

6 个解决方案

#1


54  

JLS 17.5.4 Write Protected Fields:

JLS 17.5.4写保护字段:

Normally, final static fields may not be modified. However System.in, System.out, and System.err are final static fields that, for legacy reasons, must be allowed to be changed by the methods System.setIn, System.setOut and System.setErr. We refer to these fields as being write-protected to distinguish them from ordinary final fields.

通常,可能不会修改最终的静态字段。但是,System.in,System.out和System.err是最终的静态字段,由于遗留原因,必须允许通过方法System.setIn,System.setOut和System.setErr更改这些字段。我们将这些字段称为写保护,以区别于普通的最终字段。

The compiler needs to treat these fields differently from other final fields. For example, a read of an ordinary final field is "immune" to synchronization: the barrier involved in a lock or volatile read does not have to affect what value is read from a final field. Since the value of write-protected fields may be seen to change, synchronization events should have an effect on them. Therefore, the semantics dictate that these fields be treated as normal fields that cannot be changed by user code, unless that user code is in the System class.

编译器需要以不同于其他最终字段的方式处理这些字段。例如,读取普通的最终字段对同步“免疫”:锁定或易失性读取中涉及的屏障不必影响从最终字段读取的值。由于可以看到写保护字段的值发生变化,因此同步事件应该对它们产生影响。因此,语义要求将这些字段视为用户代码无法更改的普通字段,除非该用户代码在System类中。

By the way, actually you can mutate final fields via reflection by calling setAccessible(true) on them (or by using Unsafe methods). Such techniques are used during deserialization, by Hibernate and other frameworks, etc, but they have one limitation: code that have seen value of final field before modification is not guaranteed to see the new value after modification. What's special about the fields in question is that they are free of this limitation since they are treated in special way by the compiler.

顺便说一句,实际上你可以通过在它们上面调用setAccessible(true)(或者通过使用Unsafe方法)来反射最终字段。这些技术在反序列化期间,通过Hibernate和其他框架等使用,但它们有一个限制:在修改之前看到最终字段值的代码不能保证在修改后看到新值。有关字段的特殊之处在于它们不受此限制,因为它们由编译器以特殊方式处理。

#2


28  

Java uses a native method to implement setIn(), setOut() and setErr().

Java使用本机方法来实现setIn(),setOut()和setErr()。

On my JDK1.6.0_20, setOut() looks like this:

在我的JDK1.6.0_20上,setOut()看起来像这样:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

...

private static native void setOut0(PrintStream out);

You still can't "normally" reassign final variables, and even in this case, you aren't directly reassigning the field (i.e. you still can't compile "System.out = myOut"). Native methods allow some things that you simply can't do in regular Java, which explains why there are restrictions with native methods such as the requirement that an applet be signed in order to use native libraries.

您仍然无法“正常”重新分配最终变量,即使在这种情况下,您也不会直接重新分配字段(即您仍然无法编译“System.out = myOut”)。本机方法允许您在常规Java中无法做到的一些事情,这解释了为什么本机方法存在限制,例如要求对applet进行签名以使用本机库。

#3


7  

To extend on what Adam said, here is the impl:

为了扩展亚当所说的,这里是impl:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

and setOut0 is defined as:

和setOut0定义为:

private static native void setOut0(PrintStream out);

#4


6  

Depends on the implementation. The final one may never change but it could be a proxy/adapter/decorator for the actual output stream, setOut could for example set a member that the out member actually writes to. In practice however it is set natively.

取决于实施。最后一个可能永远不会改变,但它可能是实际输出流的代理/适配器/装饰器,setOut可以例如设置out成员实际写入的成员。然而,在实践中它是本地设置的。

#5


1  

the out which is declared as final in System class is a class level variable. where as out which is in the below method is a local variable. we are no where passing the class level out which is actually a final one into this method

在System类中声明为final的out是一个类级变量。以下方法中的out是局部变量。我们没有把类级别传递给这个方法,这实际上是最后一个

public static void setOut(PrintStream out) {
  checkIO();
  setOut0(out);
    }

usage of the above method is as below:

使用上述方法如下:

System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));

now the data will be diverted to the file. hope this explanation makes the sense.

现在数据将被转移到文件中。希望这种解释有道理。

So no role of native methods or reflections here in changing purpose of the final keyword.

因此,在更改final关键字的目的时,本机方法或反射没有任何作用。

#6


0  

As far as how, we can take a look at the source code to java/lang/System.c:

至于如何,我们可以看一下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);
}

...

In other words, JNI can "cheat". ; )

换句话说,JNI可以“欺骗”。 ; )

#1


54  

JLS 17.5.4 Write Protected Fields:

JLS 17.5.4写保护字段:

Normally, final static fields may not be modified. However System.in, System.out, and System.err are final static fields that, for legacy reasons, must be allowed to be changed by the methods System.setIn, System.setOut and System.setErr. We refer to these fields as being write-protected to distinguish them from ordinary final fields.

通常,可能不会修改最终的静态字段。但是,System.in,System.out和System.err是最终的静态字段,由于遗留原因,必须允许通过方法System.setIn,System.setOut和System.setErr更改这些字段。我们将这些字段称为写保护,以区别于普通的最终字段。

The compiler needs to treat these fields differently from other final fields. For example, a read of an ordinary final field is "immune" to synchronization: the barrier involved in a lock or volatile read does not have to affect what value is read from a final field. Since the value of write-protected fields may be seen to change, synchronization events should have an effect on them. Therefore, the semantics dictate that these fields be treated as normal fields that cannot be changed by user code, unless that user code is in the System class.

编译器需要以不同于其他最终字段的方式处理这些字段。例如,读取普通的最终字段对同步“免疫”:锁定或易失性读取中涉及的屏障不必影响从最终字段读取的值。由于可以看到写保护字段的值发生变化,因此同步事件应该对它们产生影响。因此,语义要求将这些字段视为用户代码无法更改的普通字段,除非该用户代码在System类中。

By the way, actually you can mutate final fields via reflection by calling setAccessible(true) on them (or by using Unsafe methods). Such techniques are used during deserialization, by Hibernate and other frameworks, etc, but they have one limitation: code that have seen value of final field before modification is not guaranteed to see the new value after modification. What's special about the fields in question is that they are free of this limitation since they are treated in special way by the compiler.

顺便说一句,实际上你可以通过在它们上面调用setAccessible(true)(或者通过使用Unsafe方法)来反射最终字段。这些技术在反序列化期间,通过Hibernate和其他框架等使用,但它们有一个限制:在修改之前看到最终字段值的代码不能保证在修改后看到新值。有关字段的特殊之处在于它们不受此限制,因为它们由编译器以特殊方式处理。

#2


28  

Java uses a native method to implement setIn(), setOut() and setErr().

Java使用本机方法来实现setIn(),setOut()和setErr()。

On my JDK1.6.0_20, setOut() looks like this:

在我的JDK1.6.0_20上,setOut()看起来像这样:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

...

private static native void setOut0(PrintStream out);

You still can't "normally" reassign final variables, and even in this case, you aren't directly reassigning the field (i.e. you still can't compile "System.out = myOut"). Native methods allow some things that you simply can't do in regular Java, which explains why there are restrictions with native methods such as the requirement that an applet be signed in order to use native libraries.

您仍然无法“正常”重新分配最终变量,即使在这种情况下,您也不会直接重新分配字段(即您仍然无法编译“System.out = myOut”)。本机方法允许您在常规Java中无法做到的一些事情,这解释了为什么本机方法存在限制,例如要求对applet进行签名以使用本机库。

#3


7  

To extend on what Adam said, here is the impl:

为了扩展亚当所说的,这里是impl:

public static void setOut(PrintStream out) {
    checkIO();
    setOut0(out);
}

and setOut0 is defined as:

和setOut0定义为:

private static native void setOut0(PrintStream out);

#4


6  

Depends on the implementation. The final one may never change but it could be a proxy/adapter/decorator for the actual output stream, setOut could for example set a member that the out member actually writes to. In practice however it is set natively.

取决于实施。最后一个可能永远不会改变,但它可能是实际输出流的代理/适配器/装饰器,setOut可以例如设置out成员实际写入的成员。然而,在实践中它是本地设置的。

#5


1  

the out which is declared as final in System class is a class level variable. where as out which is in the below method is a local variable. we are no where passing the class level out which is actually a final one into this method

在System类中声明为final的out是一个类级变量。以下方法中的out是局部变量。我们没有把类级别传递给这个方法,这实际上是最后一个

public static void setOut(PrintStream out) {
  checkIO();
  setOut0(out);
    }

usage of the above method is as below:

使用上述方法如下:

System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));

now the data will be diverted to the file. hope this explanation makes the sense.

现在数据将被转移到文件中。希望这种解释有道理。

So no role of native methods or reflections here in changing purpose of the final keyword.

因此,在更改final关键字的目的时,本机方法或反射没有任何作用。

#6


0  

As far as how, we can take a look at the source code to java/lang/System.c:

至于如何,我们可以看一下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);
}

...

In other words, JNI can "cheat". ; )

换句话说,JNI可以“欺骗”。 ; )