2013-08-21 7 views
22

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

public class My_Class 
{ 

     My_Class new_class= new My_Class(); 
} 

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

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

+2

Пока вы не создаете 'MyClass 'в конструкторе' MyClass' (Yay для бесконечной рекурсии) нет абсолютно никакой проблемы в этом. Композитный шаблон дизайна даже основан на этом. Я действительно не понимаю, о чем идет речь. Кроме того, 'public void class' никогда не будет компилироваться. –

+0

Я знаю, что это можно сделать, но мой вопрос: не так ли, как вы используете функцию без создания функции в первую очередь. Как вы можете объяснить это кому-то, кто знает только функциональное программирование? –

+0

Какая часть процесса загрузки и создания экземпляра класса все еще неясна? – Joni

ответ

31

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

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.Это означает, что класс является первым определен, так что виртуальная машина знает, что

  1. класс называется Test существует, и что она имеет метод main и конструктор, и
  2. в Test класс имеет два статических переменные, один из которых называется x, а другой - instance, а
  3. Какова структура объекта класса Test. Другими словами: как выглядит объект; какие атрибуты у него есть. В этом случае Test не имеет атрибутов экземпляра.

Теперь, когда класс определен, это инициализировано. Прежде всего, каждому статическому атрибуту присваивается значение по умолчанию 0 или null. Это устанавливает x в 0. Затем JVM выполняет инициализаторы статического поля в порядке исходного кода. Есть два:

  1. Создать экземпляр класса Test и присвоить его instance. Существует два шага к созданию экземпляра:
    1. Первая память выделена для объекта. JVM может это сделать, поскольку он уже знает макет объекта на этапе определения класса.
    2. Для инициализации объекта вызывается конструктор Test(). JVM может сделать это, потому что у него уже есть код для конструктора из фазы определения класса. Конструктор выводит текущее значение x, которое составляет 0.
  2. Установить статическую переменную 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); 
    } 
} 
+0

Означает ли это, что выражения внутри определения класса, которые создают экземпляр собственного класса, не выполняются/не компилируются перед другими выражениями? Вы сказали, что класс уже определен в этот момент, и это правда, но как экземпляр сможет получить доступ к методу, который не определен до этой точки. Не могли бы вы подробно объяснить свой ответ, чтобы объяснить это кому-то, кто всегда использовал интерпретируемый язык (что означает, что код выполняется по очереди). –

+0

Все методы, конструкторы и другие члены определяются, когда класс определен, до того, как какой-либо из кода в класс выполняется. – Joni

+0

Пожалуйста, очистите мое замешательство здесь. Разве экземпляр класса собственного класса также не является членом класса в моем коде выше? Я пытаюсь очистить свои мысли, Джони. –

-1

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

public class Test { 

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

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

Отформатируйте фрагмент кода, пожалуйста. – leppie

+0

Это верно только в том случае, если это делается в конструкторе, для того же конструктора и безоговорочно, поскольку это приводит к бесконечной рекурсии. Если это делается вне конструктора (например, в методе «Clone»), это не применяется. Если это сделано условно, то это рекурсия, но не бесконечная рекурсия. – Servy

+0

@Servy, создавая экземпляр объекта * такого же класса во время объявления, также вызывает исключение без рекурсии и исключения stackoverflow. Это не просто конструктор. Этот код * (то же, что и вопрос) * даст исключение переполнения стека во время выполнения 'public class My_Class {My_Class new_class = new My_Class();}', плюс я не уверен, что java ведет себя по-другому, но на C# это будет исключение. – Habib

1

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

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

Представьте, что вы собрали класс, чтобы автоматически обрабатывать вызовы при перекрестном потоке, когда это необходимо. Очень важно для поточных 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 
    } 

} 
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); 
    } 
} 
Смежные вопросы