奇怪的“out”变量,System.out.println()

6
以下是来自java.lang.System类(JDK版本1.6)的代码:
public final static PrintStream out = nullPrintStream(); //out is set to 'null'

private static PrintStream nullPrintStream() throws NullPointerException {
    if (currentTimeMillis() > 0) {
        return null;
    }
    throw new NullPointerException();
}

当我们在代码中写入System.out.println("Something");时,即使'out'被设置为'null',为什么我们不会得到NullPointerException呢?
无论如何,'out'将通过System类中的以下setOut方法进行设置。
public static void setOut(PrintStream out) {
     checkIO();
     setOut0(out);
 }

他们为什么需要 nullPrintStream 方法?

如果(currentTimeMillis()> 0){返回null;} => 这真的很奇怪...在JDK 7中,它只是:public final static PrintStream out = null; - assylias
1
@assylias 这些都是为了安抚早期版本的javac/JIT编译器而存在的。如果没有那个 if 语句,编译器可能会意识到它总是返回 null 并将 out 编译为编译时常量,带来所有不良后果。 - Marko Topolnik
这意味着一旦足够的时间过去,使得currentTimeMillis()方法返回的值超出了long类型的最大值,所有在Java 7之前版本的虚拟机上运行的应用程序都将因错误而失败:java.lang.ExceptionInInitializerError Caused by java.lang.NullPointerException at java.lang.System.nullPrintStream(Unknown Source)或类似的错误。 - gparyani
请参见https://dev59.com/9G025IYBdhLWcg3whWdm。 - Raedwald
3个回答

8
看一下private static void initializeSystemClass()方法 - 这个方法被调用来启动事情,它调用setOut0()这是一个native方法。这将Stream连接到它应该在的位置。
所以即使这个字段看起来public static final,它实际上不是,native代码会改变它。 编辑 OP询问JLS为什么需要nullPrintStream方法? 这与java编译器有关 - 如果static final字段在编译时分配给某个常量(如null),则编译器将“内联”它们。编译器实际上会使用常量替换对字段的每个引用。
这会导致初始化破坏,因为对象将不再持有对Stream的引用,而是对null的引用。将流赋值给方法的返回值可以防止内联。
有些人可能会称其为肮脏的技巧。误用Bismarck的话说,“JDK就像香肠,最好不要看着它被制作出来”。

尽管如上所述,在Java 7中已经纠正了这个问题 - 所以这与Marko所指出的旧编译器有关。 - assylias
他们本可以定义 public final static PrintStream out = null;。帮助理解 "lnline" 静态常量字段。 - AmitG
2
@AmitG 请看一下这个这个。如果你将某些东西定义为编译时常量,(Java 6)编译器会使用该信息来优化代码——它将用字面值替换任何引用。因此,编译器会将任何说System.out的内容替换为null,因为它假设System.out不能改变,因为它被设置为null并且是final - Boris the Spider
如果我能的话,我会给你+2分,但现在只能给你+1分,因为你的解释太好了。最后的引用也很棒! - Nir Alfasi

2

System.in、out和err由JVM通过本地代码管理。使用nullPrintStream()的整个魔法是为了防止javac内联这些字段。自Java 7以来,情况似乎如下:

public final static PrintStream out = null;

2
这只是System.out类的初始化方式。
还有一个方法:
 private static native void setOut0(PrintStream out);

以下方法中被称为:

private static void initializeSystemClass() {

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接