web-dev-qa-db-ja.com

クラス自体の内部でクラスのインスタンスを作成するとどうなりますか?

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

public class My_Class
 {

      My_Class new_class= new My_Class();
 }

私はそれが可能であることを知っており、自分でそれをやったことがありますが、これは「誰が最初だったのですか?問題のタイプ。 JVM /コンパイラーの観点からだけでなく、プログラミングの観点からもこれを明確にする答えを受け取ることができてうれしいです。これを理解することで、OOプログラミングの非常に重要なボトルネックの概念を明確にするのに役立つと思います。

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

27

クラス自体にクラスのインスタンスを作成してもまったく問題はありません。明らかな鶏卵問題は、プログラムのコンパイル中および実行中にさまざまな方法で解決されます。

コンパイル時間

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

実行時

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

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

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

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

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

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

_x=0
x=1
_

ご覧のように、鶏や卵の問題はありません。定義と初期化の段階にクラスの読み込みを分けることで、問題を完全に回避できます。

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

_class Test {
    Test buggy = new Test();
}
_

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

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

_class Chain {
    Chain link = null;
    public Chain(int length) {
        if (length > 1) link = new Chain(length-1);
    }
}
_
37
Joni

私が常にクラス内からインスタンスを作成しているのは、ゲームのフレームを作成しているときなど、静的なコンテキストで静的でないアイテムを参照しようとしているときです。実際にフレームを設定するメソッド。また、コンストラクタ内に設定したいものがある場合にも使用できます(次のように、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);
    }
}
2
Shzylo

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

鶏と卵の問題は、再帰的な問題として解決されます。それは、より多くの作業/インスタンス/その他を生成し続けない基本ケースです。

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

イベント自体に対して1回、ステータスイベントに対して1回、それを2回処理するコードを記述するか、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
    }

}
1
felega

自己インスタンスを保持する属性は静的である必要があります

public class MyClass {

    private static MyClass instance;

    static {
        instance = new MyClass();
    }

    // some methods

}
0