Нет никакой проблемы при создании экземпляров класса в самом классе. Очевидная проблема с курицей или яйцом решается по-разному, пока программа компилируется и когда она выполняется.
Compile времени
Когда класс, который создает экземпляр себе компилируется, компилятор считает, что класс имеет circular dependency на себя. Эта зависимость легко решить: компилятор знает, что класс уже скомпилирован, поэтому он не будет пытаться скомпилировать его снова. Вместо этого он притворяется, что уже существующий класс генерирует код соответствующим образом.
времени выполнения
Самая большая проблема курица или яйцо с классом, создающего объект сам по себе, когда класс делает даже еще не существует; то есть, когда класс загружается. Эта проблема решается путем разбиения нагрузки класса на два этапа: сначала класс определяется, а затем он инициализирован.
Определение означает регистрацию класса с системой времени выполнения (JVM или CLR), чтобы он знал структуру, которую имеют объекты класса, и какой код следует запускать при вызове его конструкторов и методов.
Как только класс был определен, он инициализируется. Это делается путем инициализации статических элементов и запуска статических блоков инициализатора и других объектов, определенных на конкретном языке. Напомним, что класс уже определен в этой точке, поэтому среда выполнения знает, какие объекты класса выглядят и какой код следует запускать для их создания. Это означает, что при инициализации этого объекта нет проблем с созданием объектов класса.
Вот пример, который показывает, как инициализация класса и конкретизации взаимодействия в Java:
class Test {
static Test instance = new Test();
static int x = 1;
public Test() {
System.out.printf("x=%d\n", x);
}
public static void main(String[] args) {
Test t = new Test();
}
}
Давайте через шаг, как JVM будет запускать эту программу. Сначала JVM загружает класс Test
.Это означает, что класс является первым определен, так что виртуальная машина знает, что
- класс называется
Test
существует, и что она имеет метод main
и конструктор, и
- в
Test
класс имеет два статических переменные, один из которых называется x
, а другой - instance
, а
- Какова структура объекта класса
Test
. Другими словами: как выглядит объект; какие атрибуты у него есть. В этом случае Test
не имеет атрибутов экземпляра.
Теперь, когда класс определен, это инициализировано. Прежде всего, каждому статическому атрибуту присваивается значение по умолчанию 0
или null
. Это устанавливает x
в 0
. Затем JVM выполняет инициализаторы статического поля в порядке исходного кода. Есть два:
- Создать экземпляр класса
Test
и присвоить его instance
. Существует два шага к созданию экземпляра:
- Первая память выделена для объекта. JVM может это сделать, поскольку он уже знает макет объекта на этапе определения класса.
- Для инициализации объекта вызывается конструктор
Test()
. JVM может сделать это, потому что у него уже есть код для конструктора из фазы определения класса. Конструктор выводит текущее значение x
, которое составляет 0
.
- Установить статическую переменную
x
на 1
.
Только сейчас класс закончил загрузку. Обратите внимание, что JVM создал экземпляр класса, хотя он еще не был полностью загружен. У вас есть доказательства этого факта, потому что конструктор распечатал начальное значение по умолчанию 0
для x
.
Теперь, когда JVM загрузил этот класс, он вызывает метод main
для запуска программы. Метод main
создает еще один объект класса Test
- второй при выполнении программы. Опять же, конструктор выводит текущее значение x
, которое теперь 1
. Полный выход программы:
x=0
x=1
Как вы можете видеть, что нет никаких проблем, курица или яйцо: разделение класса нагрузки на фазы определения и инициализации полностью исключает проблему.
Как насчет того, когда экземпляр объекта хочет создать другой экземпляр, например, в коде ниже?
class Test {
Test buggy = new Test();
}
Когда вы создаете объект этого класса, снова нет неотъемлемой проблемы. JVM знает, как объект должен быть выложен в памяти, чтобы он мог выделить для него память. Он устанавливает для всех атрибутов значения по умолчанию, поэтому buggy
установлено на null
. Затем JVM начинает инициализацию объекта.Для этого он должен создать другой объект класса Test
. Как и раньше, JVM уже знает, как это сделать: он выделяет память, устанавливает атрибут null
и запускает инициализацию нового объекта ... что означает, что он должен создать третий объект того же класса, а затем четвертый, пятый и т. д., пока он не исчерпывает пространство стека или память кучи.
Здесь нет концептуальной проблемы: это просто обычный случай бесконечной рекурсии в плохо написанной программе. Рекурсию можно контролировать, например, с помощью счетчика; конструктор этого класса использует рекурсию, чтобы сделать цепочку объектов:
class Chain {
Chain link = null;
public Chain(int length) {
if (length > 1) link = new Chain(length-1);
}
}
Пока вы не создаете 'MyClass 'в конструкторе' MyClass' (Yay для бесконечной рекурсии) нет абсолютно никакой проблемы в этом. Композитный шаблон дизайна даже основан на этом. Я действительно не понимаю, о чем идет речь. Кроме того, 'public void class' никогда не будет компилироваться. –
Я знаю, что это можно сделать, но мой вопрос: не так ли, как вы используете функцию без создания функции в первую очередь. Как вы можете объяснить это кому-то, кто знает только функциональное программирование? –
Какая часть процесса загрузки и создания экземпляра класса все еще неясна? – Joni