Перейти из форума на сайт.

НовостиФайловые архивы
ПоискАктивные темыТоп лист
ПравилаКто в on-line?
Вход Забыли пароль? Первый раз на этом сайте? Регистрация
Компьютерный форум Ru.Board » Компьютеры » Прикладное программирование » Класс System (Java)

Модерирует : ShIvADeSt

 Версия для печати • ПодписатьсяДобавить в закладки

Открыть новую тему     Написать ответ в эту тему

durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
Добрый день, дорогие камрады!
 
    Намедни решил разобраться со структурой всем хорошо известного метода, который отвечает за стандартный поток вывода (обычно используется экран компьютера), то бишь, System.out.println(). В общем, сразу же становится понятно, так как это весьма очевидно, что мы запрашиваем метод println() на объекте out класса PrintStream, который, в свою очередь, является вложенным объектом класса System и тем самым создаются ассоциативные отношения (если конкретизировать, то мы имеем дело с агрегированием, так как класс System создаётся путём включения уже существующего класса PrintStream) между классами System и PrintStream.  
 
    Это всё вроде бы понятно, но, как выяснилось, переменная ссылочного типа out, которая описана в классе System, является статичной, а также, кроме всего прочего, имеет спецификатор final, то бишь, требует обязательной инициализации на этапе декларирования. Вот тут и возникают вопросы! В описании класса System я попытался найти строчку, которая бы указывала на явную инициализацию переменной оut. Кстати, по логике вещей, нам нужно не только связать ссылочную переменную с каким-то конкретным объектом, но и проинициализировать все основные поля, которыми обладает сам объект.  
Короче говоря, наткнулся на такую вот строчку:  

Код:
public final static PrintStream out = nullPrintStream();

Изначально возникла мысль, что именно метод nullPrintStream() отвечает за инициализацию объекта оut, но, как дальше выяснилось, этот метод всего лишь возвращает состояние null, либо исключение NullPointerException. На одном ресурсе вычитал, что конкретно за инициализацию отвечает метод initializeSystemClass(), который описан в классе System. В свою очередь этот метод использует для непосредственной инициализации переменной out set-метод, который описан в этом же классе и называется setOut().  
 
    А теперь основной вопрос топика, ради которого он, собственно говоря, и создавался: как JVM понимает, что ей нужно вызвать эти методы для инициализации переменной out? Где мы обращаемся к методу initializeSystemClass()? Я привык думать, что инициализацию статических полей мы должны проводить явно, а не через вызов методов (хотя могу и ошибаться из-за недостатка знаний). А любой метод мы запрашиваем внутри главного метода main(), который указывает программе на место входа. Что же класс System? В моём понимании, это обычный класс, который содержит набор полей и операций над ними (методы). Ну и где же идёт обращение к какому бы то ни было методу внутри класса? Или же обращение происходит на каком-то другом этапе? Или же я чего-то недопонимаю? %) В любом случае, огромное всем спасибо за то, что уделили мне своё внимание!

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 13:23 11-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору

Цитата:
А теперь основной вопрос топика, ради которого он, собственно говоря, и создавался: как JVM понимает, что ей нужно вызвать эти методы для инициализации переменной out?

durov55 неприятно показаться кэпом, но - внутренняя логика JVM.  
 
Область видимости конструктора класса и метода initializeSystemClass вопросов не вызвали?
 
Отвлечёмся от языка Java, т.к. я в нём не особо силён и чтобы не ошибиться, представим некий абстрактный язык.
Возьмём, например, следующий код:
 

Код:
String a = "a";
String b = "b";
String c = a + b;

 
Конкатенация строк реализована в методе String::Concat, но в приведённом выше коде нет явного вызова String::Concat, за нас его делает компилятор. Точно так же происходит и с методом initializeSystemClass. Компилятор перед main() вставляет код, который создаёт экземпляр класса System и вызывает у него инициализирующие методы и только потом управление попадает в main, т.е. в наш код.
 
Ещё раз обращу внимание - это некий абстрактный язык, не Java, но смысл, думаю понятен

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 13:39 11-07-2017 | Исправлено: NeoAnomaly, 14:22 11-07-2017
durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
to NeoAnomaly
 
    Неплохой пример с конкатенацией, начинаю немного улавливать. Хм, благодаря Вам заметил спецификатор доступа private перед сигнатурами конструктора и метода initializeSystemClass(). Выходит, что я не такой уж и внимательный, как мне казалось
    Окей, выходит, что метод initializeSystemClass() является некоторым техническим методом класса System. Поможете мне в очередной раз поправить мою невнимательность? Если это чисто технический метод, который предназначен для использования внутри класса System, то какой публичный метод класса System использует его в своей реализации? Догадываюсь, что это опять-таки является частью внутренней логики JVM и скрыто от наших глаз. Поправьте меня, пожалуйста, если я не прав.  
 
    P.S. Задумался тут немного и всплыл ещё один вопрос. Если initializeSystemClass() приватный метод, то все эти манипуляции с инициализацией статичных полей класса System могут разворачиваться исключительно внутри самого класса System, только если технический метод initializeSystemClass() не является частью реализации какого-нибудь публичного метода, который неявно вызывается из метода main(). Проблема в том, что я всё-таки не нашёл этого публичного метода, который бы использовал в своей реализации метод initializeSystemClass(). В общем, все эти манипуляции проворачиваются конкретно в классе System или всё же компилятор сам добавляет эти несколько строчек кода при вызове функции System.out.println() внутри метода main()? Буду рад, если Вы выскажете хотя бы своё предположение по этому поводу!

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 15:03 11-07-2017 | Исправлено: durov55, 15:21 11-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору

Цитата:
Если это чисто технический метод, который предназначен для использования внутри класса System, то какой публичный метод класса System его использует в своей реализации?

durov55, а почему его обязательно должен использовать какой-то публичный метод? Область видимости в языках - это ограничения для использования программистом. Как, например, с конструктором, чтобы никто не смог инстанциировать класс System, т.к. он служебный и должен быть в одном экземпляре, то же касается и спецификатора final на классе и на out, чтобы никто не смог ничего поломать.  
 
Компилятору/виртуальной машине же без разницы на все эти спецификаторы, для них это байтики в памяти, точнее структуры с которыми они могут делать всё, что необходимо.

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 15:22 11-07-2017
durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
to NeoAnomaly
 
    В общем, я уловил Вашу мысль. По крайней мере, мне так кажется. Давайте попытаемся всё это подытожить, а Вы меня снова поправите, если что не так.  
 
    Методы initializeSystemClass() и setOut() вызываются компилятором неявным образом где-то в самом начале перед передачей управления методу main(). initializeSystemClass() связывает ссылочные переменные с конкретными объектами, а setOut() инициализирует поля объекта out. Всё верно?

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 15:35 11-07-2017 | Исправлено: durov55, 15:36 11-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
durov55, т.к. я не знаком с платформой Java, то концептуально да.
Заглянул сейчас в system.java, судя по коду и комментариям - несколько моментов, которые мне кажутся важными:
 
- Методы вызываются не компилятором, а VM в случае Java. + такие вещи в байт код обычно не попадают.
- setOut() - публичный метод, который позволяет сменить поток, который изначально задаётся в initializeSystemClass()

Код:
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));

 
- стоит обратить внимание, что для фактического задания значения полю класса вызывается метод: setOut0, который является нативным. Т.е. если я правильно понял - методом JVM. И он здесь используется какраз для обхода спецификатора final на поле класса.
 

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 16:09 11-07-2017 | Исправлено: NeoAnomaly, 16:13 11-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
Можно ещё вот этот пост почитать: How System.out.println() really works про сам процесс бутстраппинга vm там нет ничего, но косвенно понятно, что происходит

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 19:22 11-07-2017 | Исправлено: NeoAnomaly, 19:24 11-07-2017
durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
to NeoAnomaly
 
    Проблема в том, что я, к превеликому сожалению, не очень силён в английском. Глазами я конечно пробежался, но какой-то осадок всё равно остался. Вы случайно не в курсе, нет ли подобных разъяснений на русском языке?  
 
    Хотелось бы поделиться своими впечатлениями от всего этого. Я реально не думал, что с обычным методом, который должен отвечать за стандартный поток вывода, возникнет столько волокиты. Если бы знал в самом начале, это и вовсе могло бы меня отпугнуть от изучения этого языка программирования %)
 
    Ну раз уж взялся, нужно закончить, тем более не первый день ищу ответ на этот, казалось бы, простой вопрос.  
 
    Хорошо, если методы вызываются непосредственно JVM, то это вносит небольшую ясность. Это справедливо для всех методов? Или же есть исключения?
Какой именно поток задаётся в initializeSystemClass()? Тот который мы передаём в параметры методу println() в качестве аргументов при вызове?  К примеру, у нас есть такой вот код:
 

Код:
System.out.println("Hello world!");

 
    Под потоком, который изначально задаётся в initializeSystemClass() Вы имеете в виду объект класса String, который я поместил в параметры метода (подразумеваю комбинацию символов, которые образовали словосочетание "Hello world!")?
 
    Если это так, то при обращении к публичному методу setOut() мы можем менять это значение, поставляя новый объект класса String?
 
    Позволю себе последний вопрос на тему потока, чтобы уж окончательно закрыть эту тему. Если я верно Вас понял, то в своём предпоследнем сообщении Вы привели следующие строчки кода, чтобы указать на то, что именно эта часть исходного текста отвечает за инициализацию потока? Да и что вообще представляет из себя эта инициализация? Под инициализацией, в данном случае, подразумевается преобразование объекта класса String непосредственно в поток вывода? Кстати, вот эти строчки кода, которые я имел в виду в этом абзаце:  
 

Код:
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);  
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));

 
    Под самый конец возник вопрос по нативному методу. Как я понимаю, если метод нативный, то мы можем наблюдать исключительно его сигнатуру, в некоторой аналогии с интерфейсами, а где же сама реализация? Читал, что такие методы могут быть написаны на языке программирования С++, а их реализация находится в каком-то другом месте. Этим как-то должен управлять JNI, но где же сам код? Таких функций может быть вагон и маленькая тележка, не думаю, что всё это спрятано в самой JVM, тем более, что мы сами можем писать нативные методы, если я ничего не путаю.
 
    В общем, вопросов особо меньше не стало, но потихоньку разбираюсь. Спасибо Вам огромное за Ваше внимание! Я Вас наверное уже утомил? Если остались силы на меня, то буду только рад Вашей помощи. Не сочтите за лесть, но Вы в какой-то мере подняли мой уровень знаний и я Вам за это премного благодарен!

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 23:31 11-07-2017 | Исправлено: durov55, 23:32 11-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
Ух, ладно по порядку )
 

Цитата:
Проблема в том, что я, к превеликому сожалению, не очень силён в английском. Глазами я конечно пробежался, но какой-то осадок всё равно остался. Вы случайно не в курсе, нет ли подобных разъяснений на русском языке?  

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

Цитата:
Я реально не думал, что с обычным методом, который должен отвечать за стандартный поток вывода, возникнет столько волокиты

На самом деле здесь нет ничего сложного, просто на начальном этапе надо переварить всю информацию. Сейчас у тебя ещё некоторая путаница в голове, в том числе и с терминологией. Некоторая "навороченность" обусловленна тем, что Java управляемый, кросплатформенный язык, вывод в стандартный поток в Windows, *nix и где бы то ни было реализуются по разному, поэтому добавлен слой абстракции.
 

Цитата:
Хорошо, если методы вызываются непосредственно JVM, то это вносит небольшую ясность. Это справедливо для всех методов?

Не совсем понял вопрос. Смущает слово "все".
 

Цитата:
Какой именно поток задаётся в initializeSystemClass()? Тот который мы передаём в параметры методу println() в качестве аргументов при вызове?

Вот здесь надо поработать над терминологией, т.к. поток вывода никак не связан с параметром метода println(). В википедии коряво, на мой взгляд, написано про потоки ввода/вывода, поэтому ссылку давать не буду, погугли какие-нибудь статьи на эту тему. Попробуй представить следующим образом:
поток ввода/вывода
под словом поток в данном случае подразумевается некая абстракция последовательного набора данных.
под словами ввода/вывода, можно для простоты рассмотреть консольное приложение. Соответственно когда ты видишь текст в консоли - это вывод, когда набираешь на клавиатуре - ввод.
Итого, когда ты у абстракции(поток вывода out) вызываешь метод println("123") ты записываешь строку "123" в набор данных, а уже дальше эти данные печатаются в консоль и ты видишь в консоли 123. То же самое из другими данными:

Код:
 
Boolean boolValue = true;
PrintStream outStream = System.out; /// получаешь абстракцию - поток вывода
outStream.println(boolValue); /// записываешь в поток данные - значение переменной
 

 
Может коряво объяснил, если не понятно, то ищи любую книжку для начинающих.
Возвращаясь к вопросу, в initializeSystemClass() задаётся стандартный поток вывода на консоль. Отсюда же следует ответ на следующий вопрос:

Цитата:
Если это так, то при обращении к публичному методу setOut() мы можем менять это значение, поставляя новый объект класса String?

Объект класса PrintStream - абстрактный набор данных.
 
Дальнейшие вопросы про инициализацию потоков пропущу, т.к. там вообще каша пошла, возможно из написанного выше станет яснее что к чему. Пиши, если что, разберёмся
 
По поводу нативных методов - они находятся в библиотеках, которые являются частью платформы Java, либо в библиотеках, которые ты сам создал.

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 09:30 12-07-2017 | Исправлено: NeoAnomaly, 09:41 12-07-2017
durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
to NeoAnomaly
 
    Насчёт английского, то тут я согласен на все 100 %, буду стараться закрыть этот пробел, тяжело работать с какой-либо документацией, когда приходится иметь с ней дело в оригинале.
 
    Когда писал насчёт вызова методов непосредственно JVM, то хотел лишь уточнить для себя, какому инструменту JDK делегирована задача по вызову методов. Я имею некоторое представление о том, через какие этапы преобразований проходит наша программа перед непосредственным запуском. Весь исходный текст преобразовывается встроенным Java-компилятором (javac) в промежуточное представление программы, содержащее инструкции, которые будем выполнять JVM. Это промежуточное представление принято называть байт-кодом Java. Сама же JVM выполняет роль интерпретатора и построчно транслирует некоторые блоки программы в машинный код с помощью JIT-технологии. Чуть раньше Вы написали о том, что методы вызываются не компилятором, а JVM, вот я и захотел уточнить. Справедливо ли это для всех методов в языке программирования Java? Или же Вы имели в виду особенности базового класса System?
 
    С потоком вроде бы разобрался, признаю свою ошибку. А корректно ли в таком случае будет называть объект класса String, который мы задаём в параметрах метода println(), элементом потока вывода данных?
 
    Насчёт нативных методов вроде бы тоже понятно, а где эти библиотеки находятся? Залезть мы в них, я так понимаю, не можем?

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 15:57 12-07-2017 | Исправлено: durov55, 16:02 12-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору

Цитата:
Справедливо ли это для всех методов в языке программирования Java? Или же Вы имели в виду особенности базового класса System?  

durov55 я имел ввиду, что если в случае с конкатенацией строк компилятор генерирует соответствующий байт код(с проверками, вызовом метода конкатенации и т.п.), то создание экземпляра класса System и вызов метода инициализации в байт коде не представлено никак. Это внутренняя логика VM - thread.cpp

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 21:17 13-07-2017
durov55



Junior Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору
to NeoAnomaly
 
    Действительно, всё это крайне интересно! Пытался найти сам файл thread.cpp на своей файловой системе, но ничего не получилось. Зато удалось изучить свежие исходники класса System. Нашёл его в папке, куда была установлена JDK. Знаете, а ведь я работал со старыми исходниками. По сравнению с 6-й версией есть некоторые изменения.  
 
    К примеру, строчка  
 

Код:
public final static PrintStream out = nullPrintStream();

 
    потеряла свою актуальность. В 8-й версии она инициализируется иначе. Ссылочная переменная не ссылается на какой бы то ни было объект, а указывает на состояние null, то бишь, наша переменная ни на что не ссылается.
 

Код:
public final static PrintStream out = null;

 
    В принципе, состояние null мы могли бы получить и без явной инициализации, но, спецификатор final обязывает нас это сделать (присвоить какое-то начальное значение переменной). Кстати, интересно то, что спецификатор final не позволит нам переопределить нашу переменную за пределами класса (даже если мы очень этого захотим).  
 
    Затем, возвращаемся к методу initializeSystemClass() из класса System. Картинка начинает потихоньку складываться в моей голове.
 
    Как я понял, то конкретно этот кусок кода из файла thread.cpp отвечает за вызов метода initializeSystemClass(). Опять таки, если я на этот раз не ошибаюсь, то именно в этом файле описана внутренняя логика виртуальной машины Java. Вот этот кусок, о котором идёт речь:  
 

Код:
static void call_initializeSystemClass(TRAPS) {
  klassOop k =  SystemDictionary::resolve_or_fail(vmSymbols::java_lang_System(), true, CHECK);
  instanceKlassHandle klass (THREAD, k);
 
  JavaValue result(T_VOID);
  JavaCalls::call_static(&result, klass, vmSymbols::initializeSystemClass_name(),
                                         vmSymbols::void_method_signature(), CHECK);
}

 
    Непосредственно в реализации метода initializeSystemClass() нас интересуют 2 строчки, которые позволяют задать и сменить поток. Окей, я понял, что поток прежде всего является абстракцией. Мы можем использовать поток ввода или вывода данных. Но всё-таки, что же конкретно Вы имели в виду, когда писали о том, что тут мы задаём конкретный поток? Имеется в виду, что мы задаём конкретно стандартный поток вывода? А что тогда подразумевается под сменой потока? Я не думаю, что имеется в виду смена потока вывода на поток ввода, хотя именно это и лезет в голову.
 

Код:
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); //здесь мы задаём выходной поток данных?
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true)); //метод, который позволяет сменить поток. Если будем менять поток, который уже задали, то на какой? Можно какой-то пример?

 
    Как обычно, крайне благодарен за то, что не оставляете один на один с этими вопросами, на которые я пока не могу ответить сам

Всего записей: 113 | Зарегистр. 06-02-2011 | Отправлено: 00:23 18-07-2017
NeoAnomaly

Full Member
Редактировать | Профиль | Сообщение | Цитировать | Сообщить модератору

Цитата:
Действительно, всё это крайне интересно! Пытался найти сам файл thread.cpp на своей файловой системе, но ничего не получилось.

Это исходники hotspot - одной из реализаций виртуальной машины Java их не должно быть на компьютере конечного пользователя.
 

Цитата:
Кстати, интересно то, что спецификатор final не позволит нам переопределить нашу переменную за пределами класса (даже если мы очень этого захотим).  

#8.3.1.2 - не только за пределами класса.
 

Цитата:
Окей, я понял, что поток прежде всего является абстракцией. Мы можем использовать поток ввода или вывода данных. Но всё-таки, что же конкретно Вы имели в виду, когда писали о том, что тут мы задаём конкретный поток? Имеется в виду, что мы задаём конкретно стандартный поток вывода? А что тогда подразумевается под сменой потока? Я не думаю, что имеется в виду смена потока вывода на поток ввода, хотя именно это и лезет в голову.  

Под конкретным поток ввода/вывода имеется ввиду - конкретная реализация абстракции потока. Если, например, нужен вывод не в консоль, а в файл? Тогда это должна быть другая реализация, которая пишет данные в файл, а не в консоль. Изменю комментарии в приведённом коде:
 

Код:
 
//здесь мы задаём выходной поток данных?  
/// Здесь мы не задаём выходной поток данных, а создаём экземпляр класса, реализующего абстракцию: "потока данных".
/// Если, например, вместо дескриптора консоли в параметры этого класса я передам дескриптор файла, то данные пойдут не
/// на консоль, а в этот файл.
FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out);  
 
//метод, который позволяет сменить поток. Если будем менять поток, который уже задали, то на какой? Можно какой-то пример?
/// В комментарии выше я уже упоминал, про файл, в коде это может выглядеть так:
/// FileOutputStream fdOut = new FileOutputStream("C://test//my_app_output.txt");  
setOut0(new PrintStream(new BufferedOutputStream(fdOut, 128), true));  
 

 
Разберись с каждым классом, который представлен в данном коде, что такое FileOutputStream/FileInputStream. почему FileOutputStream "заворачивается" в BufferedOutputStream, а потом ещё до кучи и в PrintStream. И всё должно стать окончательно понятно. Или же можно зайти с другой стороны, всётаки почитать про стандартные потоки ввода/вывода ОС, а потом уже разбираться с потоками данных(stream) языка.

Всего записей: 418 | Зарегистр. 23-03-2010 | Отправлено: 19:05 18-07-2017
Открыть новую тему     Написать ответ в эту тему

Компьютерный форум Ru.Board » Компьютеры » Прикладное программирование » Класс System (Java)


Реклама на форуме Ru.Board.

Powered by Ikonboard "v2.1.7b" © 2000 Ikonboard.com
Modified by Ru.B0ard
© Ru.B0ard 2000-2024

BitCoin: 1NGG1chHtUvrtEqjeerQCKDMUi6S6CG4iC

Рейтинг.ru