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

Jac*_*des 30 c# java compiler-construction class

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

public class My_Class
 {

      My_Class new_class= new My_Class();
 }
Run Code Online (Sandbox Code Playgroud)

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

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

Jon*_*oni 37

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

编译时

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

运行

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

定义意味着使用运行时系统(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();
    }
}
Run Code Online (Sandbox Code Playgroud)

让我们逐步了解JVM如何运行该程序.首先,JVM加载Test该类.这意味着首先定义类,以便JVM知道

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

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

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

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

现在JVM已加载此类,它调用该main方法来运行该程序.该main方法创建了另一个类对象Test- 第二个执行程序.再次构造打印出的电流值x,这是现在1.该计划的完整输出是:

x=0
x=1
Run Code Online (Sandbox Code Playgroud)

正如您所看到的,没有鸡蛋或鸡蛋问题:将类加载分为定义和初始化阶段可以完全避免问题.

当对象的实例想要创建另一个实例时,如下面的代码中那样?

class Test {
    Test buggy = new Test();
}
Run Code Online (Sandbox Code Playgroud)

当您创建此类的对象时,同样没有固有的问题.JVM知道如何在内存中布置对象,以便它可以为它分配内存.它将所有属性设置为其默认值,因此buggy设置为null.然后JVM开始初始化对象.为了做到这一点,它必须创建另一个类对象Test.像以前一样,JVM已经知道如何做到这一点:它分配内存,设置属性null,并开始初始化新对象......这意味着它必须创建同一个类的第三个对象,然后是第四个,即第五,依此类推,直到它用完堆栈空间或堆内存.

这里没有任何概念上的问题请注意:这只是编写错误的程序中无限递归的常见情况.可以例如使用计数器来控制递归; 这个类的构造函数使用递归来创建一个对象链:

class Chain {
    Chain link = null;
    public Chain(int length) {
        if (length > 1) link = new Chain(length-1);
    }
}
Run Code Online (Sandbox Code Playgroud)