Pertanyaan java: "final" System.out, System.in dan System.err?


System.out dinyatakan sebagai public static final PrintStream out.

Tetapi Anda bisa menelepon System.setOut() untuk menetapkan ulang itu.

Hah? Bagaimana ini mungkin jika itu final?

(titik yang sama berlaku untuk System.in dan System.err)

Dan yang lebih penting, jika Anda dapat memutasi bidang final statis publik, apa artinya ini sejauh jaminan (jika ada) itu final Memberi anda? (Saya tidak pernah menyadari atau mengharapkan System.in/out/err berperilaku seperti itu final variabel)


75
2018-05-10 14:16


asal


Jawaban:


JLS 17.5.4 Menulis Bidang Dilindungi:

Biasanya, bidang statis akhir tidak dapat dimodifikasi. Namun System.in, System.out, dan System.err adalah bidang statis akhir yang, untuk alasan warisan, harus diizinkan untuk diubah oleh metode System.setIn, System.setOut dan System.setErr. Kami menyebut bidang ini sebagai dilindungi tulis untuk membedakan mereka dari bidang akhir biasa.

Compiler perlu memperlakukan bidang-bidang ini secara berbeda dari bidang-bidang akhir lainnya. Sebagai contoh, pembacaan bidang akhir biasa adalah "kebal" untuk sinkronisasi: penghalang yang terlibat dalam pembacaan kunci atau tidak stabil tidak harus mempengaruhi nilai apa yang dibaca dari bidang akhir. Karena nilai bidang yang dilindungi penulisan dapat terlihat berubah, kejadian sinkronisasi harus memiliki efek pada mereka. Oleh karena itu, semantik menyatakan bahwa bidang-bidang ini diperlakukan sebagai bidang normal yang tidak dapat diubah oleh kode pengguna, kecuali bahwa kode pengguna berada di System kelas.

By the way, sebenarnya Anda bisa bermutasi final bidang melalui refleksi dengan menelepon setAccessible(true) pada mereka (atau dengan menggunakan Unsafe metode). Teknik tersebut digunakan selama deserialisasi, oleh Hibernate dan kerangka kerja lainnya, dll, tetapi mereka memiliki satu batasan: kode yang telah melihat nilai bidang akhir sebelum modifikasi tidak dijamin untuk melihat nilai baru setelah modifikasi. Apa yang khusus tentang bidang yang dimaksud adalah bahwa mereka bebas dari batasan ini karena mereka diperlakukan dengan cara khusus oleh kompilator.


55
2018-05-10 14:28



Java menggunakan metode asli untuk diterapkan setIn(), setOut() dan setErr().

Di JDK1.6.0_20 saya, setOut() terlihat seperti ini:

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

...

private static native void setOut0(PrintStream out);

Anda masih tidak dapat "biasanya" menetapkan ulang final variabel, dan bahkan dalam hal ini, Anda tidak secara langsung menetapkan ulang bidang (Anda masih tidak dapat mengkompilasi)System.out = myOut"). Metode asli memungkinkan beberapa hal yang tidak dapat Anda lakukan di Java biasa, yang menjelaskan mengapa ada pembatasan dengan metode asli seperti persyaratan bahwa applet akan ditandatangani untuk menggunakan pustaka asli.


28
2018-05-10 14:18



Untuk memperluas apa yang dikatakan Adam, di sini adalah impl:

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

dan setOut0 didefinisikan sebagai:

private static native void setOut0(PrintStream out);

7
2018-05-10 14:21



Bergantung pada implementasi. Yang terakhir mungkin tidak pernah berubah tetapi bisa berupa proxy / adapter / dekorator untuk aliran keluaran yang sebenarnya, setOut bisa misalnya menetapkan anggota yang benar-benar ditulis oleh anggota keluar. Namun dalam praktiknya, hal itu diatur secara alami.


6
2018-05-10 14:22



itu out yang dinyatakan sebagai final di kelas Sistem adalah variabel tingkat kelas. dimana yang keluar dalam metode di bawah ini adalah variabel lokal. kami tidak ada di mana melewati tingkat kelas yang sebenarnya merupakan yang terakhir dalam metode ini

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

penggunaan metode di atas adalah sebagai berikut:

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

sekarang data akan dialihkan ke file. harap penjelasan ini masuk akal.

Jadi tidak ada peran metode asli atau refleksi di sini dalam mengubah tujuan kata kunci akhir.


1
2017-12-13 16:48



Sejauh bagaimana, kita dapat melihat kode sumbernya 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);
}

...

Dengan kata lain, JNI bisa "curang". ; )


0
2018-05-05 19:20