[JAVA] [Diskussion] Warum wird System.out (in, err) mit null initialisiert, obwohl es eine Konstante ist?

In meinem ersten Jahr als neuer Absolvent bin ich immer noch mit der Ausbildung beschäftigt. Wie der Titel schon sagt, warum wird die System.out-Konstante beim Aufruf normal verwendet, obwohl sie mit null initialisiert ist? Eine solche Frage kam auf und ich dachte darüber nach (diesmal mit der out-Konstante, weil ich denke, dass andere ähnliche Implementierungen durchgeführt werden). Ich habe es versucht, also habe ich einen Artikel geschrieben.

Ich bin eine Person, die nicht so viel Java gebissen hat und die Grundlagen beherrscht. Ich betrachte es mit halbherzigem Wissen. Wenn Sie also einen Fehler machen, würde ich es begrüßen, wenn Sie mit warmen Augen weiterlesen könnten. (Wenn jemand es weiß, würde ich es begrüßen, wenn Sie einen Kommentar abgeben könnten.)

Übrigens ist meine Umgebung im Moment JDK14, aber wenn Sie sich andere Versionen ansehen, können Sie dies möglicherweise herausfinden. Ich denke, dass gleichzeitig auch auf JDK8 verwiesen wird.

Hintergrund

Ich spreche nicht wirklich über die Details, weil es so dünn ist, Zunächst werde ich kurz erklären, warum ich eine solche Frage hatte.

Vor ungefähr einem halben Monat zeigte mir der Ausbilder am Schulungsort den Inhalt der Systemklasse. Die Geschichte, dass es mit null initialisiert wird, obwohl es als öffentliches statisches Finale deklariert ist Ich fragte: "Ich kenne den Grund nicht (der Ausbilder, der mir das Training gegeben hat, hat normalerweise kein Java geschrieben). Hast du nicht so viel nachgedacht? ) Bitte lassen Sie mich wissen, wenn Sie den Grund kennen. "" Es wurde durch ein beiläufiges Thema ausgelöst.

Dann werde ich schreiben, was ich in Betracht gezogen habe.

Erste Überlegung

Um ehrlich zu sein, dachte ich, ich sollte aufgeben, weil es von Anfang an schwer zu verstehen war.

Wenn Sie nachschlagen, flirten Leute mit ähnlichen Fragen, und hier ist die erste Hit-Site "Die Tiefe von System.out" https://kappuccino-2.hatenadiary.org/entry/20080731/1217480162

Um ehrlich zu sein, ist der Artikel zu alt, um sehr hilfreich zu sein (insbesondere dachte ich, er wurde in nullPrintStream () oder irgendwo geschrieben !!). Die naive Methode wird jedoch gegen Ende leicht erwähnt, so dass es ein Hinweis sein kann. ich dachte Ich habe mich entschlossen, die System-Klasse nach einer Beschreibung einer nativen Methode zu durchsuchen, die die out-Konstante zu manipulieren scheint.

Im Moment ist hier derjenige, der zuerst verwandt zu sein scheint

private static native void setOut0(PrintStream out);

Dies liegt offensichtlich daran, dass die PrintStream-Klasse als Argument verwendet wird ... Da dies eine private Methode ist, dachte ich, dass sie in der Klasse absolut verwendet wird, also habe ich sie nachgeschlagen.

System.java


public class System {
    //Kürzung
    private static void initPhase1() {
        //Kürzung
        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")));
        //Kürzung
    }
}

Aufruf von setOut0 mit FileOutPutStream als Argument! !! !! !! (Übrigens, in JDK1.8 die Methode initializeSystemClass () Es gibt eine Beschreibung in. ) Aber auch hier stellte sich eine andere Frage. Es gibt keine bestimmte Aufgabe, und obwohl sie in erster Linie privat ist, gibt es nirgendwo einen Angerufenen.

Ich dachte, ich wäre zum Anfang zurückgekehrt, aber am Anfang der Methode steht "// Der Zeichensatz wird in System.c initialisiert und funktioniert". Es gab eine Beschreibung "nicht von den Eigenschaften abhängig".

Um ehrlich zu sein, schien es in der Google-Übersetzung sinnvoll zu sein, aber anscheinend war der Code in C-Sprache geschrieben Ich fragte mich, ob es irgendwie gelesen und initialisiert worden war, und machte mich als nächstes auf den Weg, um die c-Quelle zu finden.

Aber vorher eins. Tatsächlich wird die Methode setOut0 () auch in öffentlichen statischen void setOut (PrintStream out) verwendet. Da es öffentlich ist, können wir es tatsächlich nennen. Also habe ich es tatsächlich genannt.

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

Wenn ich diesen Code tatsächlich ausführe, wird hello.txt im selben Verzeichnis wie die Quelle erstellt, und die Zeichenfolge in System.out.println () lautet Es wird ausgegeben.

Dies wirft eindeutig meine allgemeine Weisheit um, dass Konstanten nicht umgeschrieben werden können, und ich bin ehrlich überrascht. Daraus war ich überzeugt, dass die Feldkonstante durch andere Faktoren initialisiert wurde.

Betrachten Sie einen anderen Faktor

Um ehrlich zu sein, zum Zeitpunkt der ständigen Erklärung, ohne etwas zu sagen

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

Ich dachte, ich sollte es schreiben, aber es scheint nicht so einfach zu sein. Um ehrlich zu sein, habe ich zu wenig Wissen von diesem Punkt und meine Gedanken sind nicht gut organisiert.

Die Systemklasse verfügt über eine Methode, die einen solchen PrintStream erstellt.

System.java


public class System {
    //Kürzung
    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);
    }
}

Soweit ich die Argumente sehen kann, rufe ich einen Konstruktor auf, der try-catch erfordert. (Ich denke, es könnte ein anderer Konstruktor sein, aber ich bin sicher, es gibt einen Grund, warum es dieser Konstruktor sein sollte ...)

Wie viele von Ihnen vielleicht wissen, kann Try-Catch nicht zugewiesen werden, während es in einem Ausdruck eingeschlossen ist. Können wir dann einen statischen Initialisierer verwenden? Ich dachte.

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

Rückblickend ist es ein schrecklicher Code ... Um ehrlich zu sein, ich war verwirrt und konnte nicht daran denken, also bitte vergib mir ...

Übrigens, wenn ich versuche, dies auszuführen, erhalte ich den folgenden Kompilierungsfehler.

Main.java:16:Error:Der Variablentest wurde möglicherweise nicht initialisiert
    }
    ^
1 Fehler

Vielleicht sollten die Konstanten (wahrscheinlich) direkt initialisiert werden.

Übrigens habe ich mir vor dem Schreiben überlegt, ob es mit einem statischen Initialisierer initialisiert werden kann, aber Ich bin zu folgendem Artikel gekommen. "Wenn Sie mit dem statischen Initialisierer eine Ausnahme machen, können Sie diese Klasse nicht mehr verwenden." http://java.ynworks.com/archives/46

Selbst wenn dies möglich wäre, wäre es ein Problem, wenn ein Fehler schlürfen würde und wenn er in die Systemklasse geschrieben würde, die zuerst so initialisiert würde Möglicherweise können Sie Java nicht verwenden.

Übrigens habe ich auch das Verhalten von Statik untersucht, konnte es aber nicht einholen. Ich werde vorerst einen Referenzlink veröffentlichen.

"Statische Felder können unbeabsichtigt initialisiert werden" https://mrtry.hatenablog.jp/entry/2017/10/06/103329 "[Bericht] Wie funktionieren Java-Programme?" https://qiita.com/Hoshito/items/fdc146e4e2802d587a02

Werfen Sie einen Blick auf den Angerufenen der einheimischen Methode.

Die Geschichte geht auf die native Methode zurück. Ich habe mich sehr bemüht, darüber nachzudenken, aber es scheint besser, den in anderen Sprachen implementierten Code zu betrachten, also habe ich danach gesucht.

Übrigens scheint ein System, das C-Sprache usw. von Java aus aufruft, standardmäßig bereitgestellt zu werden, und es scheint "JNI" zu heißen. https://ja.wikipedia.org/wiki/Java_Native_Interface

Zuerst dachte ich bei der Suche nach der Quelle, aber an erster Stelle gibt es einige Header-Dateien in "jdk-14 \ include". Ich habe keine Ahnung, wo die wesentliche Implementierung ist. Ich war gezwungen, im Internet nach der Quelle zu suchen. Es ist JDK6, also denke ich, dass es ziemlich alter Code ist, aber ich glaube nicht, dass es einen Unterschied in Bezug auf die Implementierung gibt. 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);
}

Hier ist der Codeauszug Um ehrlich zu sein, fällt es mir schwer zu verstehen, was ich tue, aber ich glaube, ich lese die Klasse in C und lasse sie fließen.

Übrigens scheint es möglich zu sein, den Wert einer Konstanten auch in Java durch Reflektion zu ändern. Wie ich jedoch durch Nachforschungen herausgefunden habe, scheint es, dass dem Sicherheitsmanager eine mysteriöse Kraft hinzugefügt wurde. Ich kann es scheinbar nicht ändern. Referenz (ergänzender Teil): "Dynamische Änderung des privaten statischen Endfelds in Java" https://qiita.com/5at00001040/items/83bd7ea85d0f545ae7c3

Ich denke, es ersetzt null in der C-Sprache.

~~ Und der Grund, warum am Ende null enthalten ist, ist auch wie im obigen ergänzenden Teil beschrieben Es scheint, dass der Wert der Konstante nicht geändert werden kann, da der von System.out referenzierte Typ nicht geändert werden kann. Ich war selbst davon überzeugt, dass es null war. ~~

Nachtrag

Ich habe den Grund geschrieben, warum null ohne Erlaubnis enthalten ist, aber wenn Sie sorgfältig darüber nachdenken, führt es die native Methode intern aus. Es war egal (^ _ ^;)

Ende

Das ist meine Überlegung. Um ehrlich zu sein, glaube ich nicht, dass ich das Innere des Systems kennen muss, aber ich habe mich bisher mit meiner einfachen Frage befasst. Danke für Ihre Kooperation. Um ehrlich zu sein, das Maß an Rücksichtnahme (wenn ich es zurücklese, denke ich nicht wirklich darüber nach?) Es hat aufgehört, also habe ich mich überhaupt nicht in es verliebt. Ich war jedoch zu besorgt darüber, also machte ich eine Pause und untersuchte es, aber es macht ziemlich viel Spaß. Wenn ich von nun an etwas Zeit habe, werde ich den Ablauf beim Starten von JNI und JVM untersuchen.

Wenn jemand diese Art von Thema kennt, lass es mich bitte in den Kommentaren wissen.

Vielen Dank.

Recommended Posts

[Diskussion] Warum wird System.out (in, err) mit null initialisiert, obwohl es eine Konstante ist?
Obwohl der Pfad der Eigenschaftendatei in der Option -cp angegeben ist, wird er nicht als Klassenpfad erkannt.
Das Phänomen, dass Sie sich nicht abmelden können, obwohl es in devise beschrieben ist