Подтвердить что ты не робот

Как создается экземпляр класса внутри самого класса?

Что позволяет создать экземпляр класса внутри самого класса?

public class My_Class
 {

      My_Class new_class= new My_Class();
 }

Я знаю, что это возможно и сделал это сам, но я все еще не могу заставить себя поверить, что это не что-то вроде "кто был первым - курица или яйцо?" тип проблемы. Я был бы рад получить ответ, который прояснит это с точки зрения программирования, а также с точки зрения JVM/компилятора. Я думаю, что понимание этого поможет мне понять некоторые очень важные узкие понятия программирования OO.

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

4b9b3361

Ответ 1

Нет абсолютно никакой проблемы при создании экземпляров класса в самом классе. Очевидная проблема с курицей или яйцом решается по-разному, пока программа компилируется и когда она выполняется.

время компиляции

Когда класс, который создает экземпляр сам по себе, компилируется, компилятор обнаруживает, что класс имеет круговую зависимость сам по себе. Эта зависимость легко решить: компилятор знает, что класс уже скомпилирован, поэтому он не будет пытаться скомпилировать его снова. Вместо этого он притворяется, что уже существующий класс генерирует код соответственно.

во время выполнения

Самая большая проблема с курицей или яйцом с классом, создающим объект сам по себе, - это когда класс еще не существует; то есть, когда класс загружается. Эта проблема решается путем разбития нагрузки класса на два этапа: сначала определяется класс, а затем инициализируется.

Определение означает регистрацию класса с системой времени выполнения (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. Это означает, что класс сначала определен, так что JVM знает, что

  • существует класс с именем 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);
    }
}

Ответ 2

Главное, что я всегда вижу, что я создаю экземпляр внутри класса, - это когда я пытаюсь ссылаться на нестатический элемент в статическом контексте, например, когда я создаю фрейм для игры или что-то еще, я используйте основной метод для фактической настройки фрейма. Вы также можете использовать его для того, когда в конструкторе есть что-то, что вы хотите установить (например, в следующем, я делаю свой JFrame не равным нулю):

public class Main {
    private JFrame frame;

    public Main() {
        frame = new JFrame("Test");
    }

    public static void main(String[] args) {
        Main m = new Main();

        m.frame.setResizable(false);
        m.frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        m.frame.setLocationRelativeTo(null);
        m.frame.setVisible(true);
    }
}

Ответ 3

Другие ответы в основном касались вопроса. Если это помогает обернуть мозг вокруг него, как насчет примера?

Проблема с курицей и яйцом разрешается, так как любая рекурсивная проблема заключается в следующем: базовый случай, который не позволяет производить больше работы/случаев/независимо.

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

Вы можете написать код, который обрабатывает его дважды, один раз для самого события и один раз для события состояния, или написать один раз и повторно использовать.

Большая часть класса была обрезана, поскольку это не имеет отношения к обсуждению.

public sealed class AutoInvokingEvent
{
    private AutoInvokingEvent _statuschanged;

    public event EventHandler StatusChanged
    {
        add
        {
            _statuschanged.Register(value);
        }
        remove
        {
            _statuschanged.Unregister(value);
        }
    }

    private void OnStatusChanged()
    {
        if (_statuschanged == null) return;

        _statuschanged.OnEvent(this, EventArgs.Empty);
    }


    private AutoInvokingEvent()
    {
        //basis case what doesn't allocate the event
    }

    /// <summary>
    /// Creates a new instance of the AutoInvokingEvent.
    /// </summary>
    /// <param name="statusevent">If true, the AutoInvokingEvent will generate events which can be used to inform components of its status.</param>
    public AutoInvokingEvent(bool statusevent)
    {
        if (statusevent) _statuschanged = new AutoInvokingEvent();
    }


    public void Register(Delegate value)
    {
        //mess what registers event

        OnStatusChanged();
    }

    public void Unregister(Delegate value)
    {
        //mess what unregisters event

        OnStatusChanged();
    }

    public void OnEvent(params object[] args)
    {
        //mess what calls event handlers
    }

}

Ответ 4

Атрибут для хранения собственного экземпляра должен быть статическим

public class MyClass {

    private static MyClass instance;

    static {
        instance = new MyClass();
    }

    // some methods

}

Ответ 5

Создание экземпляра объекта внутри объекта может привести к StackOverflowError, поскольку каждый раз, когда вы создаете экземпляр из этого класса "Test", вы создаете другой экземпляр и другой экземпляр и т.д. старайтесь избегать эта практика!

public class Test  {

    public Test() {  
        Test ob = new Test();       
    }

    public static void main(String[] args) {  
        Test alpha = new Test();  
    }  
}