最近Singletonデザインパターンについて調べていたが、自分自身の型への参照を持つ場合の理解が足りなかったのでさらに調べたことのメモ。
Cの構造体の場合
C言語の構造体は、自分自身の構造体へのポインタをフィールドとして含むことが出来る。構造体自体はフィールドに含むことはできない。仮に構造体自体をフィールドに含むことが出来る場合、構造体変数を宣言したときに再帰的に構造体が宣言されてメモリを食い尽くしてしまう?以下実際のコードで実験.
自分自身の構造体へのポインタを有する場合
#include<stdio.h> struct Data{ int val; struct Data *next; }; int main(){ struct Data data; data.val=10; data.next=NULL; printf("val=%d\tnext=%p\n",data.val,data.next); return 0; }
実行結果
val=10 next=0000000000000000
自分自身の構造体を有する場合、コンパイラに怒られる
#include<stdio.h> struct Data{ int val; struct Data next; }; int main(){ struct Data data; data.val=10; data.next=NULL; printf("val=%d\tnext=%p\n",data.val,data.next); return 0; }
コンパイルエラーメッセージ
.\main.c:5:17: error: field 'next' has incomplete type struct Data next;
ポインタのサイズは(どの型へのポインタであろうが処理系が同じであれば)固定なので、構造体の宣言が書き終わっていなくても問題ないということなのだろうか。
Javaのクラスの場合
そもそもこの記事を書こうと思ったのが、TECHSCORESのSingletonパターンとWikipediaのSingletonパターンの書き方が微妙に異なっていて、なぜこの二つが同じ挙動になるのかが理解できなかったからである。変数名等を揃えるとそれぞれ次のようなコードが書かれている。
TECHSCORE
public class Singleton{ private static Singleton singleton = new Singleton(); private Singleton(){} public static Singleton getInstance(){ return singleton; } }
public class Singleton{ private static Singleton singleton; private Singleton(){} public static Singleton getInstance(){ if( singleton == null){ singleton = new Singleton(); } return singleton; } }
共通して言えることは
を有している。異なるのは
- インスタンスを生成するタイミング
である。ぱっと見Wikipediaのコードの方が分かりやすい(と思う)。個人的に謎なのが
private static Singleton singleton = new Singleton();
の部分である。というのも、フィールドは変数の宣言だけを行い、インスタンスの生成はコンストラクタの中で行うものだと思っていたからである。しかし、コンストラクタの中で自身のクラスへのインスタンスを生成すると再帰的にインスタンスが生成されてしまうのでどうすればよいのだろうと悩んでいたところ、この疑問をドンピシャで答えてくれている記事が見つかった。結論から言うと、ポイントは以下のようになる。
- Javaではフィールドやローカル変数はオブジェクトの実体ではなく、オブジェクトへの参照である。したがって、宣言しただけでは大してメモリは消費しない。
- Javaではフィールドの宣言と同時に自身のクラスのコンストラクタを呼び出すことが可能で、それがstatic変数であれば一度だけインスタンスを生成することが出来る
- リスト構造のようにインスタンスを数珠つなぎにしたければコンストラクタとは別に新しいインスタンスを生成するためのメソッドを準備しておく。
まず1について。これはCの自分自身への構造体へのポインタと同じようなものだろう。
次に2.について。これはTECHSCOREのコードのような書き方は可能ですよということを言っている。つまり、static変数であればフィールドの宣言と同時にインスタンスを代入することも可能ですよーってこと。逆に、コンストラクタ内でこのような操作はできないらしい。まあ、コンストラクタはインスタンスごとに異なる値で初期化することを目的にしているのだから、クラスそのものに属するというstatic変数の性質とは相いれないということなのだろう。
最後に3.について。これはSingletonとは関係ないが再帰的にインスタンスを生成させないために必要なことである。次のような2つのコードを見てみよう。
インスタンス生成用に別メソッドを定義
public class Data{ private Data instance; private int index; public Data(){} public Data createNext(){ instance = new Data(); } }
public class Data{ private Data instance = new Data(); private int index; public Data(){ instance = new Data(); } }
最初のコードの場合、createInstance()が呼び出された時点で初めてメモリ上にインスタンスが確保されるので、インスタンス生成が無限ループになることはないが、二番目のコードでは明らかに無限ループになってしまう。Wikipediaのコードは、ゲッターであるgetInstance()がcreateInstance()の役割も担っている。
結論
オブジェクト指向むずかしい