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

80

System.outwird als deklariert public static final PrintStream out.

Sie können jedoch anrufen, System.setOut()um es neu zuzuweisen.

Huh? Wie ist das möglich, wenn es ist final?

(Gleicher Punkt gilt für System.inund System.err)

Und was noch wichtiger ist: Wenn Sie die öffentlichen statischen Endfelder mutieren können, was bedeutet dies für die Garantien (falls vorhanden), finaldie Sie erhalten? (Ich habe nie bemerkt oder erwartet, dass sich System.in/out/err als finalVariablen verhält. )

Jason S.
quelle
3
Endgültige Felder genießen von der JVM selbst nicht viele Vorteile, obwohl sie vom Prüfer streng geprüft werden. Es gibt auch Möglichkeiten, endgültige Felder zu ändern, jedoch nicht über Standard-Java-Code (da dies Gegenstand des Überprüfers ist). Es wird über Unsafe erstellt und in Java über Field.set (muss zugänglich sein true) verfügbar gemacht, das zu den genannten Unsafe-Inhalten kompiliert wird. Auch JNI kann das, daher ist die JVM nicht so sehr daran interessiert, zu optimieren ... {Vielleicht hätte ich den Kommentar als Antwort strukturieren sollen, aber meh}
bestsss

Antworten:

57

JLS 17.5.4 Schreibgeschützte Felder :

Normalerweise dürfen die endgültigen statischen Felder nicht geändert werden. Allerdings System.in, System.outund System.errsind endgültig statische Felder, die für Legacy - Gründen erlaubt werden muss durch die Verfahren geändert werden System.setIn, System.setOutund System.setErr. Wir bezeichnen diese Felder als schreibgeschützt , um sie von normalen Endfeldern zu unterscheiden.

Der Compiler muss diese Felder anders behandeln als andere endgültige Felder. Zum Beispiel ist ein Lesevorgang eines gewöhnlichen Endfelds "immun" gegen Synchronisation: Die Barriere, die an einer Sperre oder einem flüchtigen Lesevorgang beteiligt ist, muss keinen Einfluss darauf haben, welcher Wert aus einem Endfeld gelesen wird. Da sich der Wert schreibgeschützter Felder möglicherweise ändert, sollten sich Synchronisationsereignisse auf sie auswirken. 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 SystemKlasse.

Übrigens können Sie finalFelder durch Reflektion mutieren , indem Sie sie aufrufen setAccessible(true)(oder UnsafeMethoden verwenden). Solche Techniken werden während der Deserialisierung, von Hibernate und anderen Frameworks usw. verwendet, haben jedoch eine Einschränkung: Code, der den Wert des letzten Felds vor der Änderung gesehen hat, kann den neuen Wert nach der Änderung nicht garantiert sehen. Das Besondere an den betreffenden Feldern ist, dass sie von dieser Einschränkung frei sind, da sie vom Compiler auf besondere Weise behandelt werden.

axtavt
quelle
4
Möge der FSM Legacy-Code für die schöne Art und Weise segnen, wie er das zukünftige Design beeinträchtigt!
Schlitten
1
>> Diese Technik wird während der Deserialisierung verwendet << dies ist jetzt nicht wahr, Desereliazation verwendet Unsichere (schnellere)
Bests
1
Es ist wichtig zu verstehen, dass dies setAccessible(true)nur für Nichtfelder funktioniert static, was es für die Aufgabe geeignet macht, die Deserialisierung oder das Klonen von Code zu unterstützen, aber es gibt keine Möglichkeit, static finalFelder zu ändern . Aus diesem Grund beginnt der zitierte Text mit „ Normalerweise dürfen endgültige statische Felder nicht geändert werden “ und bezieht sich auf die final staticArt dieser Felder und die drei Ausnahmen. Der Fall von Instanzfeldern wird an einer anderen Stelle besprochen.
Holger
Ich frage mich, warum sie den finalModifikator nicht einfach entfernt haben . scheint einfacher als all dieses "schreibgeschützte" Zeug. Ich bin mir ziemlich sicher, dass das keine bahnbrechende Veränderung ist.
Mark VY
@Holger Es gibt eine Möglichkeit, statische Endfelder zu ändern (bis zu Java 8). öffentliche Klasse OnePrinter {private static final Integer ONE = 1; public static void printOne () {System.out.println (ONE); }} und dann können Sie Feld z = OnePrinter.class.getDeclaredField ("ONE"); z.setAccessible (true); Feld f = Field.class.getDeclaredField ("Modifikatoren"); int modifiers = z.getModifiers (); f.setAccessible (true); f.set (z, Modifikatoren & ~ Modifikator.FINAL); z.set (null, 2); OnePrinter.printOne ();
Peter Verhas
30

Java verwendet eine native Methode zu implementieren setIn(), setOut()und setErr().

Auf meinem JDK1.6.0_20 setOut()sieht es so aus:

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

...

private static native void setOut0(PrintStream out);

Sie können finalVariablen immer noch nicht "normal" neu zuweisen, und selbst in diesem Fall können Sie das Feld nicht direkt neu zuweisen (dh Sie können immer noch nicht kompilieren " System.out = myOut"). Native Methoden ermöglichen einige Dinge, die Sie in normalem Java einfach nicht tun können. Dies erklärt, warum es bei nativen Methoden Einschränkungen gibt, z. B. die Anforderung, dass ein Applet signiert werden muss, um native Bibliotheken verwenden zu können.

Adam Batkin
quelle
1
OK, es ist also eine Hintertür um die reine Java-Semantik ... könnten Sie den Teil der Frage beantworten, den ich hinzugefügt habe, nämlich ob Sie Streams neu zuweisen können, hat hier finaltatsächlich eine Bedeutung?
Jason S
1
Es ist wahrscheinlich endgültig, so dass man so etwas wie System.out = new SomeOtherImp () nicht machen kann. Sie können die Setter jedoch weiterhin mit dem nativen Ansatz verwenden, wie Sie oben sehen.
Amir Raminfar
Ich denke in diesem Fall werden die Aufrufe der nativen Methoden setIn0 und setOut0 den Wert einer endgültigen Variablen wirklich ändern, native Methoden können das wahrscheinlich ... Es ist wie die Verwendung von Cheat-Codes in Spielen: S
Danilo Tommasina
@ Danilo, ja, es ändert sich :)
bestsss
@Jason, die Unfähigkeit, es direkt festzulegen, erfordert Sicherheitsüberprüfungen beim Aufrufen von setIn / setErr. alles fair und quadratisch. Beispiel, in dem Java die letzten Felder ändert: java.util.Random (
Feldsamen
7

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

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

und setOut0 ist definiert als:

private static native void setOut0(PrintStream out);
Amir Raminfar
quelle
6

Kommt auf die Umsetzung an. Das letzte kann sich nie ändern, aber es könnte ein Proxy / Adapter / Dekorator für den eigentlichen Ausgabestream sein. SetOut könnte beispielsweise ein Mitglied festlegen, in das das out-Mitglied tatsächlich schreibt. In der Praxis wird es jedoch nativ eingestellt.

Vickirk
quelle
1

Das, outwas in der Systemklasse als final deklariert wird, ist eine Variable auf Klassenebene. Dabei ist wie in der folgenden Methode eine lokale Variable angegeben. Wir sind nicht weit davon entfernt, die Klassenstufe, die eigentlich die letzte ist, in diese Methode zu integrieren

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

Die Verwendung der obigen Methode ist wie folgt:

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

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

Daher spielen native Methoden oder Überlegungen hier keine Rolle bei der Änderung des Zwecks des endgültigen Schlüsselworts.

Krish Krishna
quelle
1
setOut0 ändert die Klassenvariable, die endgültig ist.
FGB
0

Was das angeht, können wir uns den Quellcode ansehen, um 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 "betrügen". ;; )

Radiodef
quelle
-2

Ich denke , ändert die Variable auf setout0lokaler Ebene out, sie kann die Variable auf Klassenebene nicht ändern out.

坂 田 鈴木
quelle