System.out
wird 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.in
und 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), final
die Sie erhalten? (Ich habe nie bemerkt oder erwartet, dass sich System.in/out/err als final
Variablen verhält. )
Antworten:
JLS 17.5.4 Schreibgeschützte Felder :
Übrigens können Sie
final
Felder durch Reflektion mutieren , indem Sie sie aufrufensetAccessible(true)
(oderUnsafe
Methoden 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.quelle
setAccessible(true)
nur für Nichtfelder funktioniertstatic
, 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 final
Felder 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 diefinal static
Art dieser Felder und die drei Ausnahmen. Der Fall von Instanzfeldern wird an einer anderen Stelle besprochen.final
Modifikator nicht einfach entfernt haben . scheint einfacher als all dieses "schreibgeschützte" Zeug. Ich bin mir ziemlich sicher, dass das keine bahnbrechende Veränderung ist.Java verwendet eine native Methode zu implementieren
setIn()
,setOut()
undsetErr()
.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
final
Variablen 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.quelle
final
tatsächlich eine Bedeutung?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);
quelle
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.
quelle
Das,
out
was 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 integrierenDie 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.
quelle
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". ;; )
quelle
Ich denke , ändert die Variable auf
setout0
lokaler Ebeneout
, sie kann die Variable auf Klassenebene nicht ändernout
.quelle