java: “final” System.out, System.in et System.err?

System.out est déclaré comme public static final PrintStream out .

Mais vous pouvez appeler System.setOut() pour le réaffecter.

Hein? Comment est-ce possible si c’est final ?

(même point s’applique à System.in et System.err )

Et plus important encore, si vous pouvez muter les champs finaux statiques publics, qu’est-ce que cela signifie pour les garanties (le cas échéant) que cette final vous donne? (Je n’ai jamais réalisé ni attendu que System.in/out/err se soit comporté comme des variables final )

JLS 17.5.4 Write Fields protégés :

Normalement, les champs statiques finaux ne peuvent pas être modifiés. Toutefois, System.in , System.out et System.err sont des champs statiques finaux qui, pour des raisons héritées, doivent pouvoir être modifiés par les méthodes System.setIn , System.setOut et System.setErr . Nous nous référons à ces champs comme étant protégés en écriture pour les distinguer des champs finaux ordinaires.

Le compilateur doit traiter ces champs différemment des autres champs finaux. Par exemple, une lecture d’un champ final ordinaire est “immunisée” pour la synchronisation: la barrière impliquée dans un verrou ou une lecture volatile n’a pas à affecter la valeur lue dans un champ final. Étant donné que la valeur des champs protégés en écriture peut être modifiée, les événements de synchronisation doivent avoir un effet sur eux. Par conséquent, la sémantique stipule que ces champs doivent être traités comme des champs normaux qui ne peuvent pas être modifiés par le code utilisateur, à moins que ce code utilisateur ne soit dans la classe System .

Au fait, en fait, vous pouvez muter les champs final réfléchissant en appelant setAccessible(true) sur eux (ou en utilisant des méthodes Unsafe ). Ces techniques sont utilisées lors de la désérialisation, par Hibernate et autres frameworks, etc., mais elles ont une limitation: le code qui a vu la valeur du champ final avant la modification ne garantit pas la nouvelle valeur après modification. Ce qui est spécial dans les champs en question, c’est qu’ils sont libres de cette limitation, car ils sont traités de manière spéciale par le compilateur.

Java utilise une méthode native pour implémenter setIn() , setOut() et setErr() .

Sur mon JDK1.6.0_20, setOut() ressemble à ceci:

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

Vous ne pouvez toujours pas “normalement” réaffecter les variables final , et même dans ce cas, vous ne réaffectez pas directement le champ (c.-à-d. Que vous ne pouvez toujours pas comstackr ” System.out = myOut “). Les méthodes natives permettent certaines choses que vous ne pouvez tout simplement pas faire en Java, ce qui explique pourquoi il existe des ressortingctions avec les méthodes natives telles que la nécessité de signer une applet pour utiliser des bibliothèques natives.

Pour prolonger ce que Adam a dit, voici l’implication:

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

et setOut0 est défini comme suit:

 private static native void setOut0(PrintStream out); 

Dépend de la mise en œuvre. Le dernier peut ne jamais changer, mais il peut s’agir d’un proxy / adaptateur / décorateur pour le stream de sortie réel, setOut pourrait par exemple définir un membre sur lequel le membre sortant écrit réellement. En pratique toutefois, il est défini en mode natif.

le out qui est déclaré comme final dans la classe System est une variable de niveau classe. où out qui est dans la méthode ci-dessous est une variable locale. nous ne sums pas où passer le niveau de classe qui est en fait un dernier dans cette méthode

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

L’utilisation de la méthode ci-dessus est comme ci-dessous:

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

maintenant, les données seront redirigées vers le fichier. J’espère que cette explication a du sens.

Donc, pas de rôle des méthodes natives ou des reflections ici dans le but de changer le mot clé final.

En ce qui concerne comment, nous pouvons examiner le code source de 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 (ie 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); } ... 

En d’autres termes, JNI peut “sortingcher”. ; )