[Discussion] Why is System.out (in, err) initialized with null even though it is a constant?

In my first year as a new graduate, I'm still busy with training. As the title says, why is the System.out constant used normally when called even though it is initialized with null? Such a question came up, and I thought about it (this time with the out constant. I think that other implementations are similar). I tried it, so I wrote an article.

I'm a person who hasn't bitten java so much and can do the basics. I am considering it with half-hearted knowledge, so if you make a mistake, I would appreciate it if you could continue reading with warm eyes. (If anyone knows it, I would appreciate it if you could comment.)

By the way, my environment at the moment is JDK14, but if you look at other versions, you may be able to find out. I think that JDK8 is also referenced at the same time.

Background

I don't really talk about the details because it's so thin, First of all, I will briefly explain why I had such a question.

About half a month ago, the instructor at the training site showed me the contents of the System class. The story that it is initialized with null even though it is declared as public static final I asked, "I don't know the reason (the instructor who gave me the training didn't usually write java). Didn't you think so much? ) So please let me know if you know the reason. " It was triggered by a casual topic.

Then I will write what I have considered.

First consideration

To be honest, I thought I should give up because it was difficult to understand from the beginning.

If you look it up, people who have similar questions are flirting, and here is the first hit site "Abyss of System.out" https://kappuccino-2.hatenadiary.org/entry/20080731/1217480162

To be honest, the article is too old to be very helpful (especially I thought it was written in nullPrintStream () or somewhere !!). However, the naitive method is mentioned lightly toward the end, so it may be a hint. I thought I decided to search the System class for a description of a native method that seems to be manipulating the out constant.

For the time being, here is the one that seems to be related first

private static native void setOut0(PrintStream out);

It's obviously this because it takes the PrintStream class as an argument ... Since this is a private method, I thought it was absolutely used inside the class, so I looked it up.

System.java


public class System {
    //abridgement
    private static void initPhase1() {
        //abridgement
        FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
        FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
        FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
        setIn0(new BufferedInputStream(fdIn));
        setOut0(newPrintStream(fdOut, props.getProperty("sun.stdout.encoding")));
        setErr0(newPrintStream(fdErr, props.getProperty("sun.stderr.encoding")));
        //abridgement
    }
}

Calling setOut0 with FileOutPutStream as an argument! !! !! !! (By the way, in JDK1.8, initializeSystemClass () method There is a description in. ) But here again, another question arose. There is no particular pretense to substitute, and even though it is private in the first place, there is no callee anywhere.

I thought I had returned to the beginning, but at the top of the method, "// The charset is initialized in System.c and does" There was a description "not depend on the Properties."

To be honest, it seemed like it made sense in Google Translate, but apparently the code written in C language I wondered if it was read and initialized somehow, and next I set out on a journey to find the c source.

But before that, one. Actually, the setOut0 () method is also used in public static void setOut (PrintStream out), Since it is public, we can actually call it. So I actually called it.

Main.java


import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.nio.file.Files;

public class Main {
    public static void main(String[] args) {
        try (var out = new PrintStream(new FileOutputStream(new File("hello.txt")))) {
            System.setOut(out);
            System.out.println("Hello, World");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

When I actually execute this code, hello.txt is created in the same directory as the source, and the character string in System.out.println () is It will be output.

This clearly overturns my common sense that constants cannot be rewritten, and I am honestly surprised. From this, I was convinced that the field constants were initialized by other factors.

Consider another factor

To be honest, even if you don't say this, when you declare a constant

public static final PrintStream out = newPrintStream(fdOut, props.getProperty("sun.stdout.encoding"));

I thought I should write it, but it seems that it is not such a simple thing. To be honest, I have too little knowledge from this point and my thoughts are not well organized.

The System class has a method that creates such a PrintStream.

System.java


public class System {
    //abridgement
    private static PrintStream newPrintStream(FileOutputStream fos, String enc) {
       if (enc != null) {
            try {
                return new PrintStream(new BufferedOutputStream(fos, 128), true, enc);
            } catch (UnsupportedEncodingException uee) {}
        }
        return new PrintStream(new BufferedOutputStream(fos, 128), true);
    }
}

As far as I can see the arguments, I am calling the constructor that requires try-catch. (I think it could be another constructor, but I'm sure there is a reason why it should be this constructor ...)

As many of you may know, try-catch cannot be assigned while being enclosed in an expression. Then, can we use a static initializer? I thought.

Main.java


public class Main {

    public static final Main test;

    static {
        try {
            Main a = new Main();
            test = a;
        } catch (Exception e) {
            //TODO: handle exception
        }
    }

    public Main() throws Exception {
        test();
    }

    private void test() {
        System.out.println("test");
    }

    public static void main(String[] args) {
    }
}

Looking back, it's a terrible code ... To be honest, I was confused and couldn't think of it, so please forgive me ...

By the way, when I try to run this, I get the following compilation error.

Main.java:16:error:The variable test may not have been initialized
    }
    ^
1 error

Perhaps the constants should be initialized directly (probably).

By the way, before writing this, I was thinking about whether it could be initialized with a static initializer, but I arrived at the following article. "If you make an exception with the static initializer, you can't use that class anymore." http://java.ynworks.com/archives/46

Even if it could be done, it would be a problem if an error appeared slurping, and if it was written in the System class that would be initialized first like this You may not be able to use Java.

By the way, I also investigated the behavior of static, but I couldn't catch up with it. I will post a reference link for the time being.

"Static fields can be unintentionally initialized" https://mrtry.hatenablog.jp/entry/2017/10/06/103329 "[Report] How do Java programs work?" https://qiita.com/Hoshito/items/fdc146e4e2802d587a02

Take a peek at the callee of the native method.

Let's get back to the native method. I tried my best to think about it, but I found it better to look at the code implemented in other languages.

By the way, it seems that a system that calls C language etc. from java is provided as standard, and it seems to be called "JNI". https://ja.wikipedia.org/wiki/Java_Native_Interface

First of all, I thought from searching for the source, but there are some header files in "jdk-14 \ include" in the first place. I have no idea where the essential implementation is. I was forced to search for the source on the net. Since it is JDK6, I think it is quite old code, but I think that there is probably no difference in the implementation part. http://hg.openjdk.java.net/jdk6/jdk6/jdk/file/2f21faa922fe/src/share/native/java/lang/System.c

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);
}

Here is the excerpted code To be honest, I'm having a hard time understanding what I'm doing, but I think I'm reading the class in C and letting it flow.

By the way, it seems that it is possible to change the value of a constant even in Java by using reflection. However, as I found out by investigating, it seems that a mysterious power has been added to the Security Manager. I can't seem to change it. Reference (supplementary part): "Dynamic change of private static final field in Java" https://qiita.com/5at00001040/items/83bd7ea85d0f545ae7c3

So it's like replacing null with C language.

~~ And the reason why null is included at the end, this is also inlined as described in the supplementary part above It seems that the value of the constant cannot be changed, because the type referenced by System.out cannot be changed. I was convinced by myself that it was null. ~~

Postscript

I wrote the reason why null is included without permission, but if you think about it carefully, it is executing the native method internally. It didn't matter (^ _ ^;)

end

That is my consideration. To be honest, I don't think I need to know the inside of the system, but I've dealt with my simple question so far. Thank you for your cooperation. To be honest, the level of consideration (when I read it back, I'm not really thinking about it?) It has stopped at, so I haven't fallen in love with it at all. However, I was too worried about it, so I took a break and investigated it, but it's quite fun. If I have some time from now on, I will investigate the flow of JNI and JVM startup.

If anyone knows this kind of topic, please let me know in the comments.

Thank you very much.

Recommended Posts

[Discussion] Why is System.out (in, err) initialized with null even though it is a constant?
Even though the property file path is specified in the -cp option, it is not recognized as a classpath.
Phenomenon that cannot log out even though it is described in devise