java:「最終的な」System.out、System.in、およびSystem.err?


80

System.outとして宣言されpublic static final PrintStream outます。

ただし、呼び出しSystem.setOut()て再割り当てすることはできます。

え?もしそうなら、これはどのように可能finalですか?

(同じポイントに適用されるSystem.inSystem.err

さらに重要なことに、パブリック静的最終フィールドを変更できる場合、それfinalが提供する保証(ある場合)に関して、これはどういう意味ですか?(System.in/out/errがfinal変数として動作することを認識も期待もしていませんでした)


3
最終フィールドは、ベリファイアによって厳密にチェックされますが、JVM自体による多くの利点はありません。最終フィールドを変更する方法もありますが、標準のJavaコードを使用する方法はありません(ベリファイアの対象であるため)。これはUnsafeを介して行われ、前述のUnsafeのものにコンパイルされるField.set(アクセス可能なtrueが必要)を介してJavaで公開されます。また、JNIもそれを実行できるため、JVMは最適化を試みることにそれほど熱心ではありません... {おそらく、コメントを答えとして構成する必要がありますが、
まあ

回答:


57

JLS 17.5.4書き込み保護フィールド

通常、最終的な静的フィールドは変更できません。しかしSystem.inSystem.out、およびSystem.errレガシー上の理由から、方法によって変更することが許可されている必要があり、最終的な静的フィールドですSystem.setInSystem.setOutSystem.setErr。これらのフィールドは、通常の最終フィールドと区別するために、書き込み保護されていると呼びます。

コンパイラは、これらのフィールドを他の最終フィールドとは異なる方法で処理する必要があります。たとえば、通常の最終フィールドの読み取りは同期に対して「影響を受けません」。ロックまたは揮発性読み取りに関連するバリアは、最終フィールドから読み取られる値に影響を与える必要はありません。書き込み保護されたフィールドの値が変化するように見える可能性があるため、同期イベントがそれらに影響を与えるはずです。したがって、セマンティクスでは、これらのフィールドは、ユーザーコードがSystemクラスに含まれていない限り、ユーザーコードで変更できない通常のフィールドとして扱われるように指示されています。

ちなみに、実際には、finalフィールドを呼び出すsetAccessible(true)(またはUnsafeメソッドを使用する)ことで、リフレクションを介してフィールドを変更できます。このような手法は、Hibernateやその他のフレームワークなどによって逆シリアル化中に使用されますが、1つの制限があります。変更前に最終フィールドの値を確認したコードは、変更後に新しい値を確認できるとは限りません。問題のフィールドの特別な点は、コンパイラーによって特別な方法で処理されるため、この制限がないことです。


4
FSMが、将来の設計を妥協する素敵な方法でレガシーコードを祝福しますように!
そり

1
>>この手法は逆シリアル化中に使用されます<<これは現在は当てはまりません。脱セレリア化は安全でない(より高速)を使用します
ベストス2011年

1
setAccessible(true)staticフィールドでのみ機能することを理解することが重要です。これにより、コードの逆シリアル化やクローン作成を支援するタスクに適していますが、static finalフィールドを変更する方法はありません。そのため、引用されたテキストは、これらのフィールドの性質と3つの例外を参照して、「通常、最終的な静的フィールドは変更されない可能性があります」で始まりfinal staticます。インスタンスフィールドの場合については、別の場所で説明しています。
ホルガー

なぜ彼らは単にfinal修飾子を外さなかったのだろうか。この「書き込み保護された」ものすべてよりも単純なようです。私はそれが重大な変化ではないと確信しています。
Mark VY 2018年

@Holger静的な最終フィールド(Java 8まで)を変更する方法があります。public class OnePrinter {private static final Integer ONE = 1; public static void printOne(){System.out.println(ONE); }}次に、Field z = OnePrinter.class.getDeclaredField( "ONE");を実行できます。z.setAccessible(true); フィールドf = Field.class.getDeclaredField( "modifiers"); int修飾子= z.getModifiers(); f.setAccessible(true); f.set(z、modifiers&〜Modifier.FINAL); z.set(null、2); OnePrinter.printOne();
PeterVerhas18年

30

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では簡単にできないことがいくつかあります。これは、ネイティブライブラリを使用するためにアプレットに署名する必要があるなど、ネイティブメソッドに制限がある理由を説明しています。


1
OK、それは純粋なJavaセマンティクスの裏口です...私が追加した質問の一部に答えることができますか?つまり、ストリームを再割り当てできる場合、final実際にはここで何か意味がありますか?
ジェイソンS

1
System.out = new SomeOtherImp()のようなことはできないように、おそらく最終的なものです。ただし、上記のように、ネイティブアプローチを使用してセッターを使用することはできます。
アミールラミンファー2011年

この場合、ネイティブのsetIn0メソッドとsetOut0メソッドを呼び出すと、最終変数の値が実際に変更されると思います。ネイティブメソッドはおそらくそれを行うことができます...ゲームでチートコードを使用するようなものです:S
Danilo Tommasina

@Danilo、ええ、それは変更します:)
bestsss 2011年

@Jason、直接設定できない場合は、setIn / setErrの呼び出し中にセキュリティチェックが必要です。すべて公正で正方形。javaが最終フィールドを変更する例:java.util.Random(フィールドシード)
bestsss 2011年

7

アダムが言ったことを拡張するために、ここに実装があります:

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

setOut0は次のように定義されます。

private static native void setOut0(PrintStream out);

6

実装によって異なります。最後のものは決して変更されないかもしれませんが、それは実際の出力ストリームのプロキシ/アダプタ/デコレータである可能性があります。たとえば、setOutは、outメンバーが実際に書き込むメンバーを設定できます。ただし、実際には、ネイティブに設定されます。


1

outSystemクラスでfinalとして宣言されているのはクラスレベルの変数です。ここで、以下のメソッドにあるのはローカル変数です。実際にはこのメソッドの最後のクラスレベルを渡す場所はありません

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

上記の方法の使用法は次のとおりです。

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

これで、データがファイルに転送されます。この説明が理にかなっていることを願っています。

したがって、ここでは、finalキーワードの目的を変更する際のネイティブメソッドまたはリフレクションの役割はありません。


1
setOut0は、最終的なクラス変数を変更しています。
fgb 2014

0

方法に関しては、次のソースコードを見ることができます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は「チート」することができます。; )


-2

setout0ローカルレベルの変数を変更していると思いますがout、クラスレベルの変数を変更することはできませんout

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.