Java: "final" System.out, System.in и System.err?

System.out объявляется как public static final PrintStream out.

Но вы можете вызвать System.setOut(), чтобы переназначить его.

А? Как это возможно, если он final?

(то же самое относится к System.in и System.err)

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

Ответ 1

JLS 17.5.4 Записать защищенные поля:

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

Компилятор должен обрабатывать эти поля иначе, чем другие конечные поля. Например, чтение обычного конечного поля является "неуязвимым" для синхронизации: барьер, связанный с блокировкой или изменчивым чтением, не должен влиять на то, какое значение считывается из конечного поля. Поскольку значение полей с защитой от записи может меняться, события синхронизации должны влиять на них. Поэтому семантика диктует, что эти поля рассматриваются как нормальные поля, которые не могут быть изменены с помощью кода пользователя, если только этот код пользователя не находится в классе System.

Кстати, на самом деле вы можете мутировать поля final через отражение, называя setAccessible(true) на них (или используя методы Unsafe). Такие методы используются во время десериализации, Hibernate и других фреймворков и т.д., Но у них есть одно ограничение: код, который видел значение конечного поля до модификации, не гарантированно видит новое значение после модификации. Что особенно важно в этих полях, так это то, что они свободны от этого ограничения, так как они компилируются особым образом.

Ответ 2

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, что объясняет, почему существуют ограничения с помощью собственных методов, таких как требование, чтобы апплет подписывался для использования собственных библиотек.

Ответ 3

Чтобы распространить то, что сказал Адам, вот что:

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

и setOut0 определяется как:

private static native void setOut0(PrintStream out);

Ответ 4

Зависит от реализации. Конечный, возможно, никогда не изменится, но он может быть прокси-адаптером/декоратором для фактического выходного потока. SetOut может, например, установить член, который на самом деле пишет элемент-член. На практике, однако, он установлен изначально.

Ответ 5

out, объявленный как final в классе System, является переменной уровня класса. где as out, который находится в методе ниже, является локальной переменной. мы не проходим уровень класса, который на самом деле является окончательным в этом методе

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

использование вышеуказанного метода выглядит следующим образом:

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

теперь данные будут перенаправлены в файл. надеюсь, что это объяснение имеет смысл.

Таким образом, нет никакой роли встроенных методов или отражений здесь при изменении цели последнего ключевого слова.

Ответ 6

Насколько мы можем, посмотрите исходный код на 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 может "обманывать".; )