[JAVA] [Discussion] Pourquoi System.out (in, err) est-il initialisé avec null même s'il s'agit d'une constante?

Dans ma première année en tant que nouveau diplômé, je suis toujours occupé par la formation. Comme le titre l'indique, pourquoi la constante System.out est-elle utilisée normalement lorsqu'elle est appelée même si elle a été initialisée avec null? Une telle question s'est posée, et j'y ai réfléchi (cette fois avec la constante out, car je pense que d'autres implémentations similaires sont faites). Je l'ai essayé, alors j'ai écrit un article.

Je suis une personne qui n'a pas tellement mordu Java et je peux faire les bases. J'envisage cela avec une connaissance sans enthousiasme, donc si vous faites une erreur, j'apprécierais que vous puissiez continuer à lire avec des yeux chaleureux. (Si quelqu'un le sait, je vous serais reconnaissant si vous pouviez commenter.)

Au fait, mon environnement pour le moment est JDK14, mais si vous regardez d'autres versions, vous pourrez peut-être le découvrir. Je pense que JDK8 est également référencé en même temps.

Contexte

Je ne parle pas vraiment des détails parce que c'est si mince, Tout d'abord, je vais expliquer brièvement pourquoi j'ai eu une telle question.

Il y a environ un demi-mois, l'instructeur du site de formation m'a montré le contenu de la classe System. L'histoire selon laquelle il est initialisé avec null même s'il est déclaré comme public static final J'ai demandé: "Je ne connais pas la raison (l'instructeur qui m'a donné la formation n'écrivait généralement pas java) N'as-tu pas tant réfléchi? ) Alors s'il vous plaît laissez-moi savoir si vous connaissez la raison. " Il a été déclenché par un sujet informel.

Ensuite, j'écrirai ce que j'ai considéré.

Première considération

Pour être honnête, j'ai pensé que je devais abandonner car c'était difficile à comprendre depuis le début.

Si vous recherchez, les gens qui ont des questions similaires flirtent, et voici le premier site à succès "La profondeur de System.out" https://kappuccino-2.hatenadiary.org/entry/20080731/1217480162

Pour être honnête, l'article est trop vieux pour être très utile (surtout je pensais qu'il avait été écrit en nullPrintStream () ou quelque part !!). Cependant, la méthode naitive est mentionnée légèrement vers la fin, donc cela peut être un indice. j'ai pensé J'ai décidé de rechercher dans la classe System une description d'une méthode native qui semble manipuler la constante out.

Pour le moment, voici celui qui semble être lié en premier

private static native void setOut0(PrintStream out);

C'est évidemment cela car il prend la classe PrintStream comme argument ... Comme il s'agit d'une méthode privée, je pensais qu'elle était absolument utilisée dans la classe, alors je l'ai recherchée.

System.java


public class System {
    //réduction
    private static void initPhase1() {
        //réduction
        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")));
        //réduction
    }
}

Appel de setOut0 avec FileOutPutStream comme argument! !! !! !! (À propos, dans JDK1.8, méthode initializeSystemClass () Il y a une description dans. ) Mais là encore, une autre question se pose. Il n'y a pas d'affectation particulière, et même si elle est privée en premier lieu, il n'y a aucun appelé nulle part.

Je pensais être revenu au début, mais en haut de la méthode, "// Le jeu de caractères est initialisé dans System.c et le fait" Il y avait une description «ne dépend pas des propriétés».

Pour être honnête, cela semblait logique dans la traduction de Google, mais apparemment le code écrit en langage C Je me suis demandé s'il avait été lu et initialisé d'une manière ou d'une autre, et j'ai ensuite entrepris un voyage pour trouver la source c.

Mais avant cela, un. En fait, la méthode setOut0 () est également utilisée dans le public static void setOut (PrintStream out), Puisqu'il est public, nous pouvons l'appeler. Alors je l'ai vraiment appelé.

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

Lorsque j'exécute réellement ce code, hello.txt est créé dans le même répertoire que la source, et la chaîne de caractères dans System.out.println () est Ce sera une sortie.

Cela renverse clairement ma sagesse commune selon laquelle les constantes ne peuvent être réécrites, et je suis honnêtement surpris. De là, j'étais convaincu que la constante de champ était initialisée par d'autres facteurs.

Considérez un autre facteur

Pour être honnête, au moment de la déclaration constante sans rien dire

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

J'ai pensé que je devrais l'écrire, mais il semble que ce n'est pas une chose aussi simple. Pour être honnête, j'ai trop peu de connaissances à partir de ce point et mes pensées ne sont pas bien organisées.

La classe System a une méthode qui crée un tel PrintStream.

System.java


public class System {
    //réduction
    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);
    }
}

Pour autant que je puisse voir les arguments, j'appelle un constructeur qui nécessite try-catch. (Je pense que cela pourrait être un autre constructeur, mais je suis sûr qu'il y a une raison pour laquelle ce devrait être ce constructeur ...)

Comme beaucoup d'entre vous le savent peut-être, try-catch ne peut pas être affecté tout en étant inclus dans une expression. Ensuite, pouvons-nous utiliser un initialiseur statique? J'ai pensé.

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

Avec le recul, c'est un code terrible ... Pour être honnête, j'étais confus et je ne pouvais pas y penser, alors pardonnez-moi s'il vous plaît ...

Au fait, lorsque j'essaye de l'exécuter, j'obtiens l'erreur de compilation suivante.

Main.java:16:Erreur:Le test de variable n'a peut-être pas été initialisé
    }
    ^
1 erreur

Peut-être que les constantes devraient être initialisées directement (probablement).

Au fait, avant d'écrire ceci, je me demandais s'il pouvait être initialisé avec un initialiseur statique, mais Je suis arrivé à l'article suivant. "Si vous faites une exception avec l'initialiseur statique, vous ne pouvez plus utiliser cette classe." http://java.ynworks.com/archives/46

Même si cela pouvait être fait, ce serait un problème si une erreur apparaissait, et si elle était écrite dans la classe System qui serait initialisée en premier comme ceci Vous ne pourrez peut-être pas utiliser Java.

Au fait, j'ai également étudié le comportement de l'électricité statique, mais je n'ai pas pu le rattraper. Je publierai un lien de référence pour le moment.

"Les champs statiques peuvent être initialisés par inadvertance" https://mrtry.hatenablog.jp/entry/2017/10/06/103329 "[Rapport] Comment fonctionnent les programmes Java?" https://qiita.com/Hoshito/items/fdc146e4e2802d587a02

Jetez un coup d'œil à l'appelé de la méthode native.

L'histoire remonte à la méthode native. J'ai fait de gros efforts pour y réfléchir, mais il semble préférable de regarder le code implémenté dans d'autres langues, alors je l'ai cherché.

Au fait, il semble qu'un système qui appelle le langage C, etc. à partir de java soit fourni en standard, et il semble s'appeler "JNI". https://ja.wikipedia.org/wiki/Java_Native_Interface

Tout d'abord, j'ai pensé en recherchant la source, mais en premier lieu, il y a des fichiers d'en-tête dans "jdk-14 \ include". Je n'ai aucune idée de la mise en œuvre essentielle. J'ai été obligé de rechercher la source sur le net. C'est JDK6, donc je pense que c'est du code assez ancien, mais je ne pense pas qu'il y ait de différence en termes d'implémentation. 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);
}

Voici le code extrait Pour être honnête, j'ai du mal à comprendre ce que je fais, mais je pense que je lis le cours en C et que je le laisse couler.

D'ailleurs, il semble qu'il soit possible de changer la valeur d'une constante même en Java en utilisant la réflexion. Cependant, comme je l'ai découvert en enquêtant, il semble qu'un pouvoir mystérieux ait été ajouté au gestionnaire de sécurité. Je n'arrive pas à le changer. Référence (partie supplémentaire): "Changement dynamique du champ final statique privé en Java" https://qiita.com/5at00001040/items/83bd7ea85d0f545ae7c3

Donc, je suppose qu'il remplace null en langage C.

~~ Et la raison pour laquelle null est inclus à la fin, cela est également intégré comme décrit dans la partie supplémentaire ci-dessus Il semble que la valeur de la constante ne peut pas être modifiée, car le type référencé par System.out ne peut pas être modifié. J'étais convaincu par moi-même que c'était nul. ~~

Postscript

J'ai écrit la raison pour laquelle null est inclus sans autorisation, mais si vous y réfléchissez attentivement, il exécute la méthode native en interne. Cela n'avait pas d'importance (^ _ ^;)

fin

C'est ma considération. Pour être honnête, je ne pense pas avoir besoin de connaître l'intérieur du système, mais j'ai répondu à ma simple question jusqu'à présent. Merci de votre collaboration. Pour être honnête, le niveau de considération (quand je le relis, je n'y pense pas vraiment?) Cela s'est arrêté, donc je n'en suis pas du tout tombé amoureux. Cependant, j'étais trop inquiet à ce sujet, alors j'ai pris une pause et j'ai enquêté dessus, mais c'est assez amusant. Si j'ai un peu de temps à partir de maintenant, j'étudierai le flux de démarrage de JNI et JVM.

Si quelqu'un connaît ce genre de sujet, faites-le moi savoir dans les commentaires.

Merci beaucoup.

Recommended Posts

[Discussion] Pourquoi System.out (in, err) est-il initialisé avec null même s'il s'agit d'une constante?
Même si le chemin du fichier de propriétés est spécifié dans l'option -cp, il n'est pas reconnu comme chemin de classe.
Le phénomène selon lequel vous ne pouvez pas vous déconnecter même s'il est décrit dans la conception