簡體   English   中英

如何在類本身內部創建類的實例?

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

是什么使得在類本身內部創建類的實例成為可能?

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

只有現在班級已完成加載。 請注意,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
    }

    /// <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
    }

}

保持自我實例的屬性應該是靜態的

public class MyClass {

    private static MyClass instance;

    static {
        instance = new MyClass();
    }

    // some methods

}

在對象內部創建對象的實例可能會導致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