java: “最终” System.out、System.in 和 System.err?

发布于 2024-11-06 12:45:56 字数 620 浏览 1 评论 0原文

System.out< /a> 被声明为 public static final PrintStream out

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

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

(同样的一点也适用于 System.inSystem.err

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

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

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

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

(same point applies to System.in and 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)

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(7

风启觞 2024-11-13 12:45:56

JLS 17.5.4 写保护字段

通常情况下,最终静态字段不能被修改。然而,System.inSystem.outSystem.err 是最终静态字段,由于遗留原因,必须允许更改通过 System.setInSystem.setOutSystem.setErr 方法。我们将这些字段称为“写保护”,以区别于普通的最终字段。

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

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

JLS 17.5.4 Write Protected Fields:

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.

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.

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.

青巷忧颜 2024-11-13 12:45:56

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 中无法执行的操作,这解释了为什么本机方法存在限制,例如需要对小程序进行签名才能使用本机库。

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

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

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.

神仙妹妹 2024-11-13 12:45:56

为了扩展 Adam 所说的内容,这里是 impl:

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

并且 setOut0 定义为:

private static native void setOut0(PrintStream out);

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

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

and setOut0 is defined as:

private static native void setOut0(PrintStream out);
幽蝶幻影 2024-11-13 12:45:56

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

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.

ゞ花落谁相伴 2024-11-13 12:45:56

在 System 类中声明为 Final 的 out 是类级别变量。
其中下面方法中的 out 是局部变量。
我们无法将类级别传递出去,这实际上是该方法的最后一个级别

public static void setOut(PrintStream out) {
  检查IO();
  setOut0(输出);
    }

上述方法的使用方法如下:

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

现在将数据转移到文件中。
希望这个解释有意义。

因此,本机方法或反射在改变 Final 关键字的用途方面没有任何作用。

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

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.

幻梦 2024-11-13 12:45:56

至于如何实现,我们可以看一下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可以“作弊”。 ; )

As far as how, we can take a look at the source code to 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". ; )

滴情不沾 2024-11-13 12:45:56

我认为 setout0 正在修改本地级别变量 out,它不能修改类级别变量 out

I think setout0 is modifying local level variable out, it can't modify class level variable out.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文