36

クラス自体の内部にクラスのインスタンスを作成できるのはなぜですか?

public class My_Class
 {

      My_Class new_class= new My_Class();
 }

私はそれが可能であることを知っていて、自分でやったことがありますが、これが「鶏が先か卵が先か」のようなものではないと自分に信じさせることはできません。問題の種類。プログラミングの観点からも、JVM/コンパイラの観点からも、これを明確にする回答をいただければ幸いです。これを理解することで、OO プログラミングの非常に重要なボトルネックの概念を解決するのに役立つと思います。

いくつかの回答を受け取りましたが、期待したほど明確なものはありません。

4

5 に答える 5

48

クラス自体でクラスのインスタンスを作成することにはまったく問題はありません。明らかなニワトリが先か卵が先かという問題は、プログラムのコンパイル中と実行中のさまざまな方法で解決されます。

コンパイル時

それ自体のインスタンスを作成するクラスがコンパイルされると、コンパイラは、そのクラスがそれ自体に対して循環依存関係を持っていることを検出します。この依存関係は簡単に解決できます。コンパイラは、クラスが既にコンパイルされていることを認識しているため、再度コンパイルを試みることはありません。代わりに、クラスが既に存在するふりをして、それに応じてコードを生成します。

ランタイム

それ自体のオブジェクトを作成するクラスに関する最大の鶏が先か卵が先かの問題は、クラスがまだ存在していない場合です。つまり、クラスがロードされているときです。この問題は、クラスのロードを 2 つのステップに分割することで解決されます。最初にクラスが定義され、次に初期化されます。

定義とは、クラスをランタイム システム (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クラスには 2 つの静的変数があり、1 つは と呼ばれ、もうx1つは と呼ばれます。instance
  3. Testクラスのオブジェクトレイアウトは何ですか。言い換えれば、オブジェクトがどのように見えるか。どんな属性を持っている。この場合Test、インスタンス属性はありません。

クラスが定義されたので、初期化されます。まず、デフォルト値0ornullはすべての静的属性に割り当てられます。に設定x0ます。次に、JVM は静的フィールド初期化子をソース コード順に実行します。二つあります:

  1. クラスのインスタンスを作成し、Testに割り当てinstanceます。インスタンスの作成には、次の 2 つの手順があります。
    1. 最初のメモリがオブジェクトに割り当てられます。JVM は、クラス定義フェーズからオブジェクト レイアウトを既に認識しているため、これを行うことができます。
    2. Test()コンストラクターは、オブジェクトを初期化するために呼び出されます。JVM は、クラス定義フェーズからコンストラクターのコードを既に持っているため、これを行うことができます。コンストラクターは、 の現在の値を出力しxます0
  2. 静的変数xを に設定します1

クラスのロードが完了したのは今だけです。まだ完全にはロードされていませんが、JVM がクラスのインスタンスを作成したことに注意してください。0コンストラクターが の初期デフォルト値を出力したため、この事実を証明できますx

JVM はこのクラスをロードしたので、mainメソッドを呼び出してプログラムを実行します。このmainメソッドは、クラスの別のオブジェクトを作成しますTest。これは、プログラムの実行の 2 番目です。再び、コンストラクターは の現在の値を出力します。xこれは現在1です。プログラムの完全な出力は次のとおりです。

x=0
x=1

ご覧のとおり、ニワトリが先か卵が先かという問題はありません。クラスのロードを定義フェーズと初期化フェーズに分離することで、この問題を完全に回避できます。

以下のコードのように、オブジェクトのインスタンスが別のインスタンスを作成したい場合はどうでしょうか?

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

このクラスのオブジェクトを作成する場合も、固有の問題はありません。JVM は、オブジェクトをメモリに配置する方法を認識しているため、オブジェクトにメモリを割り当てることができます。すべての属性をデフォルト値に設定するため、buggyに設定されnullます。次に、JVM がオブジェクトの初期化を開始します。これを行うには、 class の別のオブジェクトを作成する必要がありますTest。以前と同様に、JVM はすでにその方法を知っています。メモリを割り当て、属性を に設定しnull、新しいオブジェクトの初期化を開始します... つまり、同じクラスの 3 番目のオブジェクトを作成し、次に 4 番目のオブジェクトを作成する必要があります。 5 番目、というように、スタック スペースまたはヒープ メモリが不足するまで続きます。

ここに概念的な問題はありません。これは、不適切に作成されたプログラムでの無限再帰の一般的なケースにすぎません。再帰は、たとえばカウンターを使用して制御できます。このクラスのコンストラクターは、再帰を使用してオブジェクトのチェーンを作成します。

class Chain {
    Chain link = null;
    public Chain(int length) {
        if (length > 1) link = new Chain(length-1);
    }
}
于 2013-08-21T15:12:23.690 に答える
1

他の回答は、ほとんどの質問をカバーしています。それが脳を包み込むのに役立つなら、例はどうですか?

ニワトリが先か卵が先かという問題は、再帰的な問題と同様に解決されます。つまり、より多くの作業/インスタンス/その他を生成し続けない基本ケースです。

必要に応じてクロススレッド イベントの呼び出しを自動的に処理するクラスを作成したとします。スレッド化された WinForms に大きく関係します。次に、何かがハンドラーに登録または登録解除されるたびに発生するイベントをクラスに公開する必要があり、当然、クロススレッド呼び出しも処理する必要があります。

それを処理するコードを 2 回 (イベント自体に対して 1 回、ステータス イベントに対して 1 回) 記述するか、1 回記述して再利用することができます。

クラスの大部分は、議論にあまり関係がないため削除されています。

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
    }

}
于 2013-09-18T20:41:53.017 に答える
0

オブジェクト内でオブジェクトのインスタンスを作成すると、StackOverflowError が発生する可能性があります。これは、この " " クラスからインスタンスを作成するたびに、Test別のインスタンスと別のインスタンスなどを作成することになるためです.. この慣行は避けてください!

public class Test  {

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

    public static void main(String[] args) {  
        Test alpha = new Test();  
    }  
}
于 2013-08-21T15:17:14.060 に答える