Frage Java: "final" System.out, System.in und System.err?


System.out wird als deklariert public static final PrintStream out.

Aber du kannst anrufen System.setOut() um es neu zuzuweisen.

Hä? Wie ist das möglich, wenn es ist? final?

(Derselbe Punkt gilt für System.in und System.err)

Und was noch wichtiger ist, wenn Sie die öffentlichen statischen Endfelder mutieren können, was bedeutet dies soweit die Garantien (falls vorhanden) das sind final gibt Ihnen? (Ich habe nie realisiert oder erwartet, dass System.in/out/err sich als final Variablen)


75
2018-05-10 14:16


Ursprung


Antworten:


JLS 17.5.4 Geschützte Felder schreiben:

Normalerweise können endgültige statische Felder nicht geändert werden. jedoch System.in, System.out, und System.err sind endgültige statische Felder, die aus Legacy-Gründen durch die Methoden geändert werden dürfen System.setIn, System.setOut und System.setErr. Wir bezeichnen diese Felder als schreibgeschützt um sie von gewöhnlichen Endfeldern zu unterscheiden.

Der Compiler muss diese Felder anders als andere Endfelder behandeln. Zum Beispiel ist ein Lesen eines gewöhnlichen Endfeldes "immun" gegenüber einer Synchronisation: Die Barriere, die an einer Sperre oder einem flüchtigen Lesen beteiligt ist, muss nicht beeinflussen, welcher Wert von einem letzten Feld gelesen wird. Da sich der Wert von schreibgeschützten Feldern ändern kann, sollten Synchronisationsereignisse Auswirkungen auf sie haben. Daher schreibt die Semantik vor, dass diese Felder als normale Felder behandelt werden, die nicht durch Benutzercode geändert werden können, es sei denn, dieser Benutzercode befindet sich in der System Klasse.

Übrigens können Sie tatsächlich mutieren final Felder durch Reflexion durch Aufruf setAccessible(true) auf sie (oder durch Verwendung von Unsafe Methoden). Solche Techniken werden bei der Deserialisierung, bei Hibernate und anderen Frameworks usw. verwendet, haben jedoch eine Einschränkung: Code, der den Wert des letzten Feldes vor der Änderung gesehen hat, kann nicht garantiert werden, dass der neue Wert nach der Änderung angezeigt wird. Das Besondere an den fraglichen Feldern ist, dass sie frei von dieser Einschränkung sind, da sie vom Compiler in besonderer Weise behandelt werden.


55
2018-05-10 14:28



Java verwendet eine native Methode zur Implementierung setIn(), setOut() und setErr().

Auf meinem JDK1.6.0_20, setOut() sieht aus wie das:

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

...

private static native void setOut0(PrintStream out);

Sie können immer noch nicht "normal" neu zuweisen final Variablen, und selbst in diesem Fall werden Sie das Feld nicht direkt neu zuweisen (d. h. Sie können immer noch nicht kompilieren "System.out = myOut"). Native Methoden erlauben einige Dinge, die Sie in regulärem Java nicht tun können, weshalb es Einschränkungen bei nativen Methoden gibt, wie die Anforderung, dass ein Applet signiert sein muss, um native Bibliotheken zu verwenden.


28
2018-05-10 14:18



Um das zu erweitern, was Adam gesagt hat, hier ist der Impl:

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

und setOut0 ist definiert als:

private static native void setOut0(PrintStream out);

7
2018-05-10 14:21



Hängt von der Implementierung ab. Die letzte darf sich nie ändern, aber es könnte ein Proxy / Adapter / Dekorator für den tatsächlichen Ausgabestrom sein, setOut könnte beispielsweise ein Mitglied festlegen, in das das out-Mitglied tatsächlich schreibt. In der Praxis ist es jedoch nativ eingestellt.


6
2018-05-10 14:22



das out was in der System-Klasse als final deklariert ist, ist eine Variable auf Klassenebene. wo als out, was in der folgenden Methode ist, ist eine lokale Variable. Wir sind nicht da, wo die Klassenstufe, die tatsächlich eine letzte ist, in diese Methode übergeht

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

Verwendung der oben genannten Methode ist wie folgt:

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

Jetzt werden die Daten in die Datei umgeleitet. Ich hoffe, diese Erklärung macht Sinn.

Also keine Rolle von nativen Methoden oder Überlegungen hier im sich ändernden Zweck des endgültigen Schlüsselworts.


1
2017-12-13 16:48



Soweit wie, können wir uns den Quellcode anschauen 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);
}

...

Mit anderen Worten, JNI kann "schummeln". ; )


0
2018-05-05 19:20