java: “остаточний” System.out, System.in та System.err?


80

System.outоголошується як public static final PrintStream out.

Але ви можете зателефонувати, System.setOut()щоб перепризначити його.

А? Як це можливо, якщо це final?

(те саме стосується System.inі System.err)

І що ще важливіше, якщо ви можете мутувати загальнодоступні статичні кінцеві поля, що це означає щодо гарантій (якщо такі є), які finalвам дають? (Я ніколи не усвідомлював і не очікував, що System.in/out/err поводився як finalзмінна)


3
Останні поля не користуються великими перевагами від самого JVM, хоча вони суворо перевіряються верифікатором. Є способи модифікувати і остаточні поля, але не за допомогою стандартного коду Java (оскільки це тема верифікатора). Це робиться через Unsafe і виставляється у java через Field.set (потребує доступу true), який компілюється до згаданих небезпечних матеріалів. Також JNI може це зробити, отже, JVM не так прагне намагатися оптимізувати ... {можливо, я мав би структурувати коментар як відповідь, але meh}
bestsss

Відповіді:


57

JLS 17.5.4 Запис захищених полів :

Зазвичай остаточні статичні поля не можуть бути змінені. Однак System.in, System.outі System.errє остаточними статичними полями, яким із застарілих причин потрібно дозволити змінювати методи System.setIn, System.setOutта System.setErr. Ми називаємо ці поля захищеними від запису, щоб відрізнити їх від звичайних кінцевих полів.

Компілятор повинен поводитися з цими полями інакше, ніж з іншими кінцевими полями. Наприклад, зчитування звичайного кінцевого поля є "імунітетом" до синхронізації: бар'єр, який бере участь у блокуванні або мінливому зчитуванні, не повинен впливати на те, яке значення зчитується з кінцевого поля. Оскільки значення захищених від запису полів може змінюватися, події синхронізації повинні впливати на них. Отже, семантика диктує, що ці поля повинні розглядатися як звичайні поля, які не можна змінити за допомогою коду користувача, якщо тільки цей код користувача не знаходиться в Systemкласі.

До речі, насправді ви можете мутувати finalполя за допомогою відображення, викликаючи setAccessible(true)їх (або використовуючи Unsafeметоди). Такі методи використовуються під час десериалізації, Hibernate та іншими фреймворками тощо, але вони мають одне обмеження: код, який бачив значення остаточного поля до модифікації, не гарантовано бачить нове значення після модифікації. Особливість розглянутих полів полягає в тому, що вони позбавлені цього обмеження, оскільки компілятор обробляє їх особливим чином.


4
Нехай FSM благословить застарілий код за прекрасний спосіб, який компрометує майбутній дизайн!
Сани

1
>> Цей прийом використовується під час десериалізації << це зараз не відповідає дійсності, десерелізація використовує небезпечний (швидший)
bestsss

1
Важливо розуміти, що setAccessible(true)працює лише для не- staticполів, що робить його придатним для завдання десеріалізації або клонування коду, але немає можливості змінити static finalполя. Ось чому цитований текст починається з „ Зазвичай кінцеві статичні поля не можуть бути змінені ”, посилаючись на final staticприроду цих полів та три винятки. Справа полів екземпляра обговорюється в іншому місці.
Holger

Цікаво, чому вони просто не зняли finalмодифікатор; здається простішим за всі ці "захищені від запису" речі. Я майже впевнений, що це не надзвичайна зміна.
Mark VY

@Holger Є спосіб змінити статичні кінцеві поля (до Java 8). відкритий клас OnePrinter {приватний статичний фінал Ціле число ONE = 1; public static void printOne () {System.out.println (ONE); }}, а потім ви можете поле z = OnePrinter.class.getDeclaredField ("ОДИН"); z.setAccessible (true); Поле f = Field.class.getDeclaredField ("модифікатори"); модифікатори int = z.getModifiers (); f.setAccessible (true); f.set (z, модифікатори & ~ Modifier.FINAL); z.set (null, 2); OnePrinter.printOne ();
Пітер Верхас,

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
Добре, отже, це задні двері навколо чистої семантики Java ... чи можете ви відповісти на частину запитання, яке я додав, а саме, якщо ви можете перепризначити потоки, чи finalнасправді це має якесь значення?
Jason S

1
Це, мабуть, остаточно, так що не можна робити щось на зразок System.out = new SomeOtherImp (). Але ви все ще можете використовувати сетери, використовуючи рідний підхід, як ви бачите вище.
Амір Рамінфар

Гадаю, у цьому випадку виклики власних методів setIn0 та setOut0 дійсно змінять значення кінцевої змінної, рідні методи, мабуть, можуть це зробити ... Це все одно, що використовувати чіт-коди в іграх: S
Данило Томмасіна

@Danilo, так, це змінюється :)
bestsss

@Jason, неможливість встановити його безпосередньо вимагає перевірки безпеки під час виклику setIn / setErr. всі справедливі та квадратні. Приклад, коли java змінює кінцеві поля: java.util.Random (насіння поля)
bestsss

7

Щоб продовжити те, що сказав Адам, ось імпл:

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

і setOut0 визначається як:

private static native void setOut0(PrintStream out);

6

Залежить від реалізації. Останній може ніколи не змінитися, але це може бути проксі / адаптер / декоратор для фактичного потоку виводу, setOut може, наприклад, встановити член, до якого фактично пише вихідний член. На практиці, однак, це встановлено спочатку.


1

значення, outяке оголошено як остаточне у класі System, є змінною рівня класу. де як out, що в наведеному нижче методі, є локальною змінною. ми не де передаємо рівень класу, який насправді є остаточним, у цей метод

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

використання вищевказаного методу наведено нижче:

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

тепер дані будуть перенаправлені у файл. сподіваюся, це пояснення має сенс.

Тож ніякої ролі власних методів чи роздумів тут у зміні призначення кінцевого ключового слова.


1
setOut0 модифікує змінну класу, яка є остаточною.
fgb

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.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.