简体   繁体   English

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

[英]How does creating a instance of class inside of the class itself works?

What makes it possible to create a instance of class inside of the class itself? 是什么使得在类本身内部创建类的实例成为可能?

public class My_Class
 {

      My_Class new_class= new My_Class();
 }

I know it is possible and have done it myself but I cannot still make myself believe that this is not something like " who was first--Chicken or egg ?" 我知道这是可能的并且已经自己完成但是我仍然不能让自己相信这不是“谁是第一个 - 鸡还是鸡蛋?” type of problem. 问题的类型。 I could be glad to receive an answer that will clarify this from programming perspective as well as from JVM/ compiler perspective. 我很高兴收到一个答案,从编程角度以及从JVM /编译器的角度来澄清这一点。 I think understanding this will help me clear some very important bottleneck concepts of OO programming. 我认为理解这将有助于我清除OO编程的一些非常重要的瓶颈概念。

I have received some answers but none are clear to the degree I expected. 我收到了一些答案,但没有一个清楚我所期望的程度。

There is absolutely no problem in creating instances of a class in the class itself. 在类本身中创建类的实例绝对没有问题。 The apparent chicken-or-egg problem is solved in different ways while the program is being compiled and when it is being run. 在编译程序和运行程序时,明显的鸡或蛋问题以不同的方式解决。

Compile-time 编译时

When a class that creates an instance of itself is being compiled, the compiler finds that the class has a circular dependency on itself. 当正在编译创建自身实例的类时,编译器会发现该类对其自身具有循环依赖性 This dependency is easy to solve: the compiler knows that the class is already being compiled so it won't try to compile it again. 这种依赖很容易解决:编译器知道该类已经被编译,所以它不会再尝试编译它。 Instead, it pretends that the class already exists generates code accordingly. 相反,它假装已经存在的类相应地生成代码。

Run-time 运行

The biggest chicken-or-egg problem with a class creating an object of itself is when the class does not even exist yet; 创建自身对象的类中最大的鸡或蛋问题是当类甚至还不存在时; that is, when the class is being loaded. 也就是说,当加载类时。 This problem is resolved by breaking class loading into two steps: first the class is defined and then it is initialized . 通过将类加载分为两个步骤来解决此问题:首先定义类,然后初始化它。

Defining means registering the class with the runtime system (JVM or CLR), so that it knows the structure that objects of the class have, and what code should be run when its constructors and methods are called. 定义意味着使用运行时系统(JVM或CLR)注册类,以便它知道类的对象具有的结构,以及在调用其构造函数和方法时应运行的代码。

Once the class has been defined it is initialized. 一旦定义了类,就会初始化它。 This is done by initializing static members and running static initializer blocks and other things defined in the particular language. 这是通过初始化静态成员和运行静态初始化程序块以及以特定语言定义的其他内容来完成的。 Recall that the class is already defined at this point, so the runtime knows what objects of the class look like and what code should be run to create them. 回想一下,此时已经定义了类,因此运行时知道类的哪些对象是什么样的,以及应该运行什么代码来创建它们。 This means there's no problem whatsoever to create objects of the class when initializing it. 这意味着在初始化类时创建类的对象没有任何问题。

Here's an example that illustrates how class initialization and instantiation interact in Java: 这是一个示例,说明了类初始化和实例化如何在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();
    }
}

Let's step through how the JVM would run this program. 让我们逐步了解JVM如何运行该程序。 First the JVM loads the Test class. 首先,JVM加载Test类。 This means that the class is first defined , so that the JVM knows that 这意味着首先定义类,以便JVM知道

  1. a class called Test exists and that it has a main method and a constructor, and that 一个名为Test的类存在,它有一个main方法和一个构造函数
  2. the Test class has two static variables, one called x and another called instance , and Test类有两个静态变量,一个叫做x ,另一个叫做instance ,和
  3. what is the object layout of the Test class. 什么是Test类的对象布局。 In other words: what an object looks like; 换句话说:对象是什么样的; what attributes it has. 它有什么属性。 In this case Test doesn't have any instance attributes. 在这种情况下, Test没有任何实例属性。

Now that the class is defined, it is initialized . 现在已定义类,它已初始化 First of all, the default value 0 or null is assigned to every static attribute. 首先,为每个静态属性分配默认值0null This sets x to 0 . 这将x设置为0 Then the JVM executes the static field initializers in the source code order. 然后JVM以源代码顺序执行静态字段初始值设定项。 There are two: 那里有两个:

  1. Create an instance of the Test class and assign it to instance . 创建Test类的实例并将其分配给instance There are two steps to instance creation: 实例创建有两个步骤:
    1. First memory is allocated for the object. 为该对象分配第一个内存。 The JVM can do this because it already knows the object layout from the class definition phase. JVM可以这样做,因为它已经知道了类定义阶段的对象布局。
    2. The Test() constructor is called to initialize the object. 调用Test()构造函数来初始化对象。 The JVM can do this because it already has the code for the constructor from the class definition phase. JVM可以这样做,因为它已经从类定义阶段获得了构造函数的代码。 The constructor prints out the current value of x , which is 0 . 构造函数打印出x的当前值,即0
  2. Set static variable x to 1 . 将静态变量x设置为1

Only now the class has finished loading. 只有现在班级已完成加载。 Notice that the JVM created an instance of the class, even though it was not fully loaded yet. 请注意,JVM创建了该类的实例,即使它尚未完全加载。 You have proof of this fact because the constructor printed out the initial default value 0 for x . 你有这个事实的证据,因为构造函数打印出x的初始默认值0

Now that the JVM has loaded this class, it calls the main method to run the program. 现在JVM已经加载了这个类,它调用main方法来运行程序。 The main method creates another object of class Test - the second in the execution of the program. main方法创建了Test类的另一个对象 - 第二个执行程序的对象。 Again the constructor prints out the current value of x , which is now 1 . 构造函数再次打印出x的当前值,现在为1 The full output of the program is: 该计划的完整输出是:

x=0
x=1

As you can see there is no chicken-or-egg problem: the separation of class loading into definition and initialization phases avoids the problem completely. 正如您所看到的,没有鸡蛋或鸡蛋问题:将类加载分为定义和初始化阶段可以完全避免问题。

What about when an instance of the object wants to create another instance, like in the code below? 当对象的实例想要创建另一个实例时,如下面的代码中那样?

class Test {
    Test buggy = new Test();
}

When you create an object of this class, again there is no inherent problem. 当您创建此类的对象时,同样没有固有的问题。 The JVM knows how the object should be laid out in memory so it can allocate memory for it. JVM知道对象应该如何在内存中布局,以便它可以为它分配内存。 It sets all the attributes to their default values, so buggy is set to null . 它将所有属性设置为其默认值,因此将buggy设置为null Then the JVM starts initializing the object. 然后JVM开始初始化对象。 In order to do this it must create another object of class Test . 为了做到这一点,它必须创建类Test另一个对象。 Like before, the JVM already knows how to do that: it allocates the memory, sets the attribute to null , and starts initializing the new object... which means it must create a third object of the same class, and then a fourth, a fifth, and so on, until it either runs out of stack space or heap memory. 像以前一样,JVM已经知道如何做到这一点:它分配内存,将属性设置为null ,并开始初始化新对象...这意味着它必须创建同一个类的第三个对象,然后是第四个,第五个,依此类推,直到它耗尽堆栈空间或堆内存。

There is no conceptual problem here mind you: this is just a common case of an infinite recursion in a badly written program. 这里没有任何概念上的问题请注意:这只是编写错误的程序中无限递归的常见情况。 The recursion can be controlled for example using a counter; 可以例如使用计数器来控制递归; the constructor of this class uses recursion to make a chain of objects: 这个类的构造函数使用递归来创建一个对象链:

class Chain {
    Chain link = null;
    public Chain(int length) {
        if (length > 1) link = new Chain(length-1);
    }
}

The main thing I always see myself creating an instance from within the class, is when I am trying to reference a non-static item in a static context, such as when I am making a frame for a game or whatever, I use the main method to actually set up the frame. 我总是看到自己在类中创建实例的主要原因是当我尝试在静态上下文中引用非静态项时,例如当我为游戏制作框架或其他什么时,我使用main实际设置框架的方法。 You can also use it for when there is something in a constructor that you want to set (like in the following, I make my JFrame not equal to null): 你也可以在你想要设置的构造函数中使用它时(如下所示,我使我的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);
    }
}

Other responses have mostly covered the question. 其他答复主要涉及这个问题。 If it helps wrap a brain around it, how about an example? 如果它有助于围绕它缠绕大脑,那么一个例子怎么样?

The chicken-and-egg problem is resolved as any recursive problem is: the basis case which doesn't keep producing more work / instances / whatever. 鸡和蛋问题得到解决,因为任何递归问题都是:基础情况不会产生更多的工作/实例/无论如何。

Imagine you've put together a class to automatically handle cross-thread event invocation when necessary. 想象一下,你已经组建了一个类来在必要时自动处理跨线程事件调用。 Heavily relevant for threaded WinForms. 与线程WinForms密切相关。 Then you'd like the class to expose an event which occurs whenever something registers or unregisters with the handler, and naturally it should handle cross-thread invocation as well. 然后你希望这个类公开一个事件,只要有东西注册或取消注册处理程序就会发生,当然它也应该处理跨线程调用。

You could write the code that handles it twice, once for the event itself and once for the status event, or write once and re-use. 您可以编写两次处理它的代码,一次用于事件本身,一次用于状态事件,或者编写一次并重复使用。

The majority of the class has been trimmed out as it isn't really relevant to the discussion. 由于与讨论无关,因此该课程的大部分内容已被删除。

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
    }

}

The attribute to hold self instance should be static 保持自我实例的属性应该是静态的

public class MyClass {

    private static MyClass instance;

    static {
        instance = new MyClass();
    }

    // some methods

}

Creating a instance of an object inside the object could lead into a StackOverflowError since every time that you create an instance from this " Test " class you will be creating another instance and another instance and so on.. try to avoid this practice! 在对象内部创建对象的实例可能会导致StackOverflowError,因为每次从这个“ Test ”类创建实例时,您将创建另一个实例和另一个实例,依此类推......尝试避免这种做法!

public class Test  {

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

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM