Java: "окончательный" System.out, System.in и System.err? - PullRequest
75 голосов
/ 10 мая 2011

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

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

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

(та же точка применима к System.in и System.err)

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

Ответы [ 7 ]

55 голосов
/ 10 мая 2011

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

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

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

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

28 голосов
/ 10 мая 2011

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

7 голосов
/ 10 мая 2011

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

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

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

private static native void setOut0(PrintStream out);
6 голосов
/ 10 мая 2011

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

1 голос
/ 13 декабря 2012

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

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

использование вышеуказанного метода, как показано ниже:

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

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

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

0 голосов
/ 15 октября 2018

Я думаю, setout0 изменяет локальную переменную уровня out, она не может изменять переменную уровня класса out.

0 голосов
/ 05 мая 2015

Что касается того, как, мы можем взглянуть на исходный код для 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 может «обманывать».;)

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...