_System.out
_ は_public static final PrintStream out
_として宣言されています。
ただし、 System.setOut()
を呼び出して再割り当てすることはできます。
え? final
の場合、これはどのように可能ですか?
(同じ点が_System.in
_と_System.err
_にも当てはまります)
さらに重要なことに、パブリック静的最終フィールドを変更できる場合、final
が提供する保証(ある場合)に関して、これはどういう意味ですか? (System.in/out/errがfinal
変数として動作することを認識も期待もしていませんでした)
通常、最終的な静的フィールドは変更できません。ただし、_
System.in
_、_System.out
_、および_System.err
_は最終的な静的フィールドであり、レガシーの理由から、メソッド_System.setIn
_、_System.setOut
_、および_System.setErr
_。これらのフィールドをwrite-protectedと呼び、通常の最終フィールドと区別します。コンパイラは、これらのフィールドを他の最終フィールドとは異なる方法で処理する必要があります。たとえば、通常の最終フィールドの読み取りは同期に対して「耐性」があります。ロックまたは揮発性読み取りに関連するバリアは、最終フィールドから読み取られる値に影響を与える必要はありません。書き込み保護されたフィールドの値が変化するように見える可能性があるため、同期イベントがそれらに影響を与えるはずです。したがって、セマンティクスでは、これらのフィールドは、ユーザーコードが
System
クラスにない限り、ユーザーコードで変更できない通常のフィールドとして扱われるように指示されています。
ちなみに、実際には、フィールドでsetAccessible(true)
を呼び出す(またはfinal
メソッドを使用する)ことで、リフレクションを介してUnsafe
フィールドを変更できます。このような手法は、Hibernateやその他のフレームワークなどによって逆シリアル化中に使用されますが、1つの制限があります。変更前に最終フィールドの値を確認したコードは、変更後に新しい値を確認できるとは限りません。問題のフィールドの特別な点は、コンパイラーによって特別な方法で処理されるため、この制限がないことです。
Javaは、ネイティブメソッドを使用してsetIn()
、setOut()
、およびsetErr()
を実装します。
私のJDK1.6.0_20では、setOut()
は次のようになります。
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
...
private static native void setOut0(PrintStream out);
final
変数を「通常」再割り当てすることはできません。この場合でも、フィールドを直接再割り当てすることはできません(つまり、「System.out = myOut
」をコンパイルすることはできません)。ネイティブメソッドでは、通常のJavaでは簡単にできないことがいくつかあります。これは、ネイティブライブラリを使用するためにアプレットに署名する必要があるなど、ネイティブメソッドに制限がある理由を説明しています。
アダムが言ったことを拡張するために、ここに実装があります:
public static void setOut(PrintStream out) {
checkIO();
setOut0(out);
}
setOut0は次のように定義されます。
private static native void setOut0(PrintStream out);
実装によって異なります。最後のものは決して変更されないかもしれませんが、それは実際の出力ストリームのプロキシ/アダプタ/デコレータである可能性があります。たとえば、setOutは、outメンバーが実際に書き込むメンバーを設定できます。ただし、実際には、ネイティブに設定されます。
システムクラスでfinalとして宣言されているout
は、クラスレベルの変数です。ここで、以下のメソッドにあるのはローカル変数です。実際にはこのメソッドの最後のクラスレベルを渡す場所はありません
public static void setOut(PrintStream out) { checkIO(); setOut0(out); }
上記の方法の使用法は次のとおりです。
System.setOut(new PrintStream(new FileOutputStream("somefile.txt")));
これで、データがファイルに転送されます。この説明が理にかなっていることを願っています。
したがって、ここでは、finalキーワードの目的を変更する際のネイティブメソッドまたはリフレクションの役割はありません。
方法に関しては、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);
}
...
言い換えれば、JNIは「チート」することができます。 ; )