В источнике System.java стандартные потоки ввода, вывода и ошибок объявляются окончательными и инициализированными нулями?

    public final static InputStream in = null;
    public final static PrintStream out = null;
    public final static PrintStream err = null;

Но, как мы очень хорошо знаем, эти потоки подключены к консоли по умолчанию и уже открыты. Существуют также методы в System class setIn(), setOut и setErr() для перенаправления потоков. Как это возможно, когда они были объявлены окончательными и установлены на значение инициализации null?

Я скомпилировал следующий код, установил точку останова при вызове println() и отладил с помощью netbeans. Моя цель состояла в том, чтобы точно определить, когда переменная System.in инициализируется стандартным выходом, вступая в исходный код. Но кажется, что выходной поток уже инициализирован к моменту вызова основного метода.

public static void main(String[] args) {
        System.out.println("foo");
}

Ответ 1

Они позже устанавливаются нативными методами SetIn0, SetOut0 и SetErr0

private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);

вызванный методом initializeSystemClass, который в соответствии с JavaDoc вызывается после инициализации потока.

FileInputStream fdIn = new FileInputStream(FileDescriptor.in);
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err);
setIn0(new BufferedInputStream(fdIn));
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));
setErr0(new PrintStream(new BufferedOutputStream(fdErr, 128), true));

Ответ 2

Это делается для предотвращения "взлома". Эти поля могут быть изменены только соответствующими сеттерами, которые вызывают методы native

private static native void setIn0(InputStream in);
private static native void setOut0(PrintStream out);
private static native void setErr0(PrintStream err);

Нативные методы могут делать все, включая изменение конечных полей.

Ответ 3

Поля

final не обязательно постоянны. Их все еще можно манипулировать, это просто то, что манипуляции предотвращаются только во время компиляции, в частности, путем предотвращения использования оператора присваивания (=). См. этот вопрос и JLS §17.5.3, в частности:

final поля могут быть изменены с помощью отражения и других зависимых от реализации средств.

Это необходимо для таких вещей, как десериализация. Это также может вызвать некоторые интересные предостережения, поскольку компиляторы могут оптимизировать поля final во время компиляции и времени выполнения. Пример JLS, приведенный выше, имеет пример этого.