Конструктор - это специальный метод, который предназначается для первичной установки значений полей объекта.
На первый взгляд конструкторы объектов не сильно отличаются от обычных методов объекта. И действительно внутри конструктора мы можем делать всё то же, что и в обычных методах объекта: выводить текст в консоль, обращаться ко всем полям и методам нового объекта, выбрасывать исключения и так далее. Так же как и обычные методы, конструкторы могут иметь аргументы. Так же как и перегруженных методов, конструкторов может быть несколько с различными сигнатурами. Так же как и дженерик-методы конструкторы могут быть параметризованы переменными типов. Даже если мы заглянем в байт-код генерируемый компилятором, в месте где должен быть вызов конструктора, мы обнаружим обращение к некоторому методу с именем
<init>
вызов которого не отличается от вызова других методов объекта. А найдя байт-код этого метода мы обнаружим, что он и содержит результат компиляции нашего конструктора. Кажется что отличий от обычных методов не много, но они есть, и довольно существенные.
Для начала давайте разберёмся, а для чего нам собственно нужны конструкторы?
Для хранения и обработки каких либо данных, будь то примитивные типы, массивы, или объекты нам необходим некоторый объём памяти. Это могут быть регистры процессора, место на стеке, либо кусочек пространства, выделенный в секции данных процесса, либо в динамически размещаемой части памяти (куче). Во многих языках программирования, в целях ускорения, при запросе программой нового кусочка памяти, память отдавалась программе не отчищенной, и могла содержать произвольные данные, которые были сохранены в этой ячейки памяти ранее. Подготовка и запись в такой кусок памяти необходимых значений, чтобы в итоге там оказался какая-либо осмысленная структура данных, ложилась целиком на плечи программиста. Вполне естественно программисты хотели облегчить себе жизнь и писали подпрограммы для инициализации (то есть установки начальных значений) для часто используемых структур данных. Такие подпрограммы применялись практически постоянно, так что создатели языка Java, решили сделать подобные подпрограммы инициализации обязательными для вызова при создании объектов, и назвали их конструкторами.
Когда в Java создаётся новый объект происходит следующее:
Сначала менеджер памяти Java выделяет объём памяти необходимый для размещения объекта. При этом учитываются не только поля объявленные непосредственно в классе создаваемого объекта, но так же поля объявленные во всех предках этого класса. Дополнительно в этот объем включается пространство для размещения структур которые используются Java-машиной для внутренних нужд. Все поля такой "заготовки" автоматически устанавливаются в дефолтные значения - null
для ссылочных типов, 0
для чисел и false
для boolean
. После этого, автоматически вызывается конструктор класса, задача которого установить начальные значения полей объекта.
Если в обычном методе первый оператор может быть любым, то у конструктора гораздо меньше свободы. Первым оператором конструктора должен быть либо явный вызов другого конструктора того же класса, либо явный или неявный вызов конструктора родительского класса. Явный вызов конструкторов того же класса осуществляется с помощью ключевого слова this
за которым следует набор аргументов заключённый в скобки. Явный вызов конструктора родительского класса производится точно так же, но при этом используется ключевое слово super
. В аргументах явного вызова конструктора того же, либо родительского класса нельзя обращаться к полям и методам объекта, равно как и использовать ключевые слова this
и super
, так как явный вызов конструктора вводит статический контекст. Для неявного вызова конструктора родительского класса писать ничего не надо, но при этом неявно вызывается конструктор по-умолчанию, который должен существовать и быть видимым для текущего класса. При этом, следует иметь ввиду, что если цепочка вызова родительских конструкторов прервётся до того как конструктор класса Object
, находящийся на вершине цепочки, успешно завершит свою работу, то объект не будет финализируемым, то есть метод finalize()
такого объекта никогда вызван не будет.
После завершения работы конструктора родительского класса, управление неявно передаётся на блоки инициализаторов экземпляра и инициализаторы полей экземпляра текущего класса. Инициализаторы исполняются в том порядке, в каком они встречаются в тексте программы. Лишь после завершения работы инициализаторов управление передаётся оставшейся части конструктора.
Остальные особенности конструкторов касаются модели памяти Java.
Если класс, либо один из его предков, переопределяет метод finalize()
, то завершение работы конструктора случится до (happens-before) запуска метода finalize()
.
Если какой-либо поток увидел ссылку на объект после завершения работы конструктора, то гарантируется что этот поток увидит корректно инициализированные final
-поля объекта, инициализация которых произошла до завершения работы конструктора.
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ