如何在类本身内部创建类的实例?

是什么使得在类本身内部创建类的实例成为可能?

public class My_Class { My_Class new_class= new My_Class(); } 

我知道这是可能的并且已经自己完成了但是我仍然不能让自己相信这不是“谁是第一个 – 鸡还是鸡蛋?” 问题的类型。 我很高兴收到一个答案,从编程角度以及从JVM /编译器的角度来澄清这一点。 我认为理解这将有助于我清除OO编程的一些非常重要的瓶颈概念。

我收到了一些答案,但没有一个清楚我所期望的程度。

在类本身中创建类的实例绝对没有问题。 在编译程序和运行程序时,明显的鸡或蛋问题以不同的方式解决。

编译时

当正在编译创建自身实例的类时,编译器会发现该类对其自身具有循环依赖性 。 这种依赖很容易解决:编译器知道该类已经被编译,所以它不会再尝试编译它。 相反,它假装已经存在的类相应地生成代码。

运行

创建自身对象的类中最大的鸡或蛋问题是当类甚至还不存在时; 也就是说,当加载类时。 通过将类加载分为两个步骤来解决此问题:首先定义类,然后初始化它。

定义意味着使用运行时系统(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知道

  1. 一个名为Test的类存在,它有一个main方法和一个构造函数
  2. Test类有两个静态变量,一个叫做x ,另一个叫做instance ,和
  3. 什么是Test类的对象布局。 换句话说:对象是什么样的; 它有什么属性。 在这种情况下, Test没有任何实例属性。

现在已经定义了类,它已被初始化 。 首先,为每个静态属性分配默认值0null 。 这将x设置为0 。 然后JVM以源代码顺序执行静态字段初始值设定项。 那里有两个:

  1. 创建Test类的实例并将其分配给instance 。 实例创建有两个步骤:
    1. 为该对象分配第一个内存。 JVM可以这样做,因为它已经知道了类定义阶段的对象布局。
    2. 调用Test()构造函数来初始化对象。 JVM可以这样做,因为它已经从类定义阶段获得了构造函数的代码。 构造函数打印出x的当前值,即0
  2. 将静态变量x设置为1

只有现在class级已经完成加载。 请注意,JVM创建了该类的实例,即使它尚未完全加载。 你有这个事实的证据,因为构造函数打印出x的初始默认值0

现在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); } } 

我总是看到自己在类中创建实例的主要原因是当我尝试在静态上下文中引用非静态项时,例如当我为游戏制作框架或其他什么时,我使用main实际设置框架的方法。 你也可以在你想要设置的构造函数中使用它时(如下所示,我使我的JFrame不等于null):

 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); } } 

其他答复主要涉及这个问题。 如果它有助于将大脑包裹起来,那么一个例子怎么样?

鸡和蛋的问题得到解决,因为任何递归问题都是:基础情况不会产生更多的工作/实例/无论如何。

想象一下,您已经组建了一个类,以便在必要时自动处理跨线程事件调用。 与线程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 } ///  /// Creates a new instance of the AutoInvokingEvent. ///  /// If true, the AutoInvokingEvent will generate events which can be used to inform components of its status. 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 } } 

在对象内部创建对象的实例可能会导致StackOverflowError,因为每次从这个“ Test ”类创建实例时,您将创建另一个实例和另一个实例,依此类推……尽量避免这种做法!

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