<このシリーズの、前の記事 | このシリーズの目次 | このシリーズの、次の記事>
スタティックメンバークラスではだめなとき、シングルトンが使える
話題: Javaプログラミング言語
クラス毎に同時にはただ1つの状態だけが必要な場合、スタティックメンバークラスはしばしば便利だ
「スタティックメンバークラス」というのは何のことだ?
スタティックフィールドとスタティックメソッドだけを持つクラスのことだ、以下のように。
public class Test1StaticMembersClass {
private static String s_valueA = "initial value A";
private Test1StaticMembersClass () {
}
public static void setValueA (String a_valueA) {
s_valueA = a_valueA;
}
public static void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", s_valueA));
}
}
ネストされたクラスのことではないわけだ。
そう。
スタティックメンバークラスは、クラスインスタンスを生成する必要がなく、以下のように使える。
public class Test1TestClass {
public void test () {
Test1StaticMembersClass.setValueA ("changed value A");
Test1StaticMembersClass.printValueA ();
}
}
スタティックメンバークラスは、同時にはただ1つの状態だけを持つが、状態は経時的に変更することができる。
システム全体が、どの時点においても、正しいただ1つの状態を見ることが重要だ。
値が不変でよいのであれば、インスタンスフィールドを使って、以下のようにすることもできる。
public class Test2InstanceMembersClass {
private String i_valueA = "constant value A";
public void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", i_valueA));
}
}
public class Test2TestClass {
public void test () {
(new Test2InstanceMembersClass ()).printValueA ();
}
}
'Test2InstanceMembersClass'のインスタンスを複数生成することができるが、どのインスタンスの値も同じ単一の値であることが保証されている。フィールドがプライベートでセッターメソッドがないから。しかし、値を変えることはできない。
それに、スタティックメンバークラスでは、値のインスタンスが1つだけであるという恩恵があり、これはメモリスペースを無駄にしないということを意味する。
上の例では、メモリスペースの無駄は実質上ゼロであるように思えるが、状態が大きなメモリスペースを占めるという場合もあり得る。
それに、状態の初期化が多量のCPUリソースを必要とするかもしれない。
かもしれない。
これらの恩恵を、フィールドをスタティックであると宣言するだけで得られる。
しかし、スタティックメンバークラスを拡張したいとき、我々はジレンマに直面するかもしれない。例えば、サブクラスでフィールドの値を上書きしたいかもしれない。
たぶんそれは、別のサブクラスには別の値を持たせたいということだろう?
そうだ。だから、同じ1つのスーパークラスフィールドをサブクラス間で共有するのは意味がなく、以下のように、いわゆる、シャドーイングをしなければならないだろう。
public abstract class Test3BaseStaticMembersClass {
protected static String s_valueA = "initial value A";
public static void setValueA (String a_valueA) {
s_valueA = a_valueA;
}
public static void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", s_valueA));
}
}
public class Test3ExtendedStaticMembersClass extends Test3BaseStaticMembersClass {
protected static String s_valueA = "overwritten value A";
private Test3ExtendedStaticMembersClass () {
}
}
ははあ . . .
しかし、これを以下のように使うと、結果は私が望むものではない。
public class Test3TestClass {
public void test () {
Test3ExtendedStaticMembersClass.printValueA ();
}
}
クラスメンバーアクセス解決のルールを考えれば、そうなるだろう。
我々は、ある表現によって、どのクラスメンバーがアクセスされるかを決定するルールについて話している。
そうだ。例えば、上の'printValueA'内の表現、's_valueA'によって、'Test3BaseStaticMembersClass'の's_valueA'がアクセスされるのか、'Test3ExtendedStaticMembersClass'の's_valueA'がアクセスされるのか?
ここで、クラスメンバーアクセス解決のルールを徹底的に復習しようと思ったのだが、どうも、長すぎる脱線になってしまうようだ。そこで、ここでは、スタティックメンバーに対してのみのクラスメンバーアクセス解決のルールを復習しよう(任意のメンバーに対してのルールは、将来の記事で復習する)。
いいだろう。
第1に、表現中の、メンバー名への修飾を見なければならない。
'修飾'というのは何のことだ?
表現が'Test3ExtendedStaticMembersClass.s_valueA'であるとき、'Test3ExtendedStaticMembersClass'がメンバー名、's_valueA'への修飾だ。
単に、's_valueA'と言ったのでは、コンパイラもランタイムもどの's_valueA'なのかを判断できない。だから、常に、何らかの修飾がなければならない。
上のコードでは、表現は単に's_valueA'であって、何の修飾もない。
その場合、我々は、暗黙の修飾があると考える。暗黙の修飾を明るみに出すことで、ルールを合理的に理解できる。
オーケー。それでは、第1に、我々は、表現中の、メンバー名への修飾を見る、それが明示的であろうが、暗黙的であろうが。それからどうする?
もしも、修飾のクラスがそのメンバー名のスタティックメンバーを持っていれば、そのメンバーがアクセスされる。そうでなければ、それはここで取り扱っている範疇にはない。インスタンスメンバーがアクセスされるか、コンパイルエラーが起きるだろうから。
「修飾のクラス」というのはどういう意味なのか?
修飾がクラスの場合、その、クラスそのものがそれだ。
そうだと思った。
修飾が変数の場合、変数タイプがそれだ。
以下の例を考えてみよう。
Test3BaseStaticMembersClass l_object = new Test3ExtendedStaticMembersClass ();
変数'l_object'にとって、'Test3BaseStaticMembersClass'が( 'Test3ExtendedStaticMembersClass'ではなく)、変数タイプだ。
そう。我々は、'Test3ExtendedStaticMembersClass'のことをインスタンスタイプと呼ぶ。
いいだろう。
修飾が表現の場合、表現タイプがそれだ。例えば、修飾'( (Test3BaseStaticMembersClass) new Test3ExtendedStaticMembersClass ())'において、'Test3BaseStaticMembersClass'がそれだ。
ははあ。すると、修飾のクラスはインスタンスタイプのことではないことを知ることが重要だ。
そう。
短く言えば、修飾の表現タイプが修飾のクラスだと言えるだろう。どんなクラスや変数も一種の表現だから。
すると、残っているのは、「暗黙の修飾が何か?」ということだ。
表現がスタティックコンテキストの中にある場合、暗黙の修飾は、表現が埋め込まれているクラスだ。表現がインスタンスコンテキストの中にある場合、暗黙の修飾は、'this'だ。
スタティックメソッドの中は典型的なスタティックコンテキストであり、インスタンスメソッドの中は典型的なインスタンスコンテキストだな?
そうだ。別の言い方では、'this'が使える任意の場所がインスタンスコンテキストで、他のところがスタティックコンテキストだ。
オーケー。
したがって、上の'printValue'内の表現、's_valueA'がどのクラスメンバーに解決されるかを知るには、我々は、第1に、修飾が暗黙であることを認識し、次に、表現がスタティックメソッド内にあるので、コンテキストがスタティックでであることを認識する。そこで、我々は、修飾が、表現が埋め込まれているクラスである'Test3BaseStaticMembersClass'であることを理解し、修飾のクラスが'Test3BaseStaticMembersClass'であることを理解する。最後に、我々は、'Test3BaseStaticMembersClass'が、指定された名前のスタティックメンバーを持っていることを認識する。
だから、'Test3BaseStaticMembersClass'の's_valueA'がアクセスされる、'Test3ExtendedStaticMembersClass'のではなく。
「1文か数文で簡単に説明できないのか?」といった苦情が予測できる。まあ、できなくはない、もしも、一部の例外を隠す直感的な説明をしようとするならば。私は、どんな例外もない、常に真である説明をしようとした。
まあいいだろう。
それでは、'test3'のケースをどうすればよいのだろうか?まあ、1つの方法は、サブクラスで'printValueA'を再定義することだろう。
スーパークラスの'printValueA'の全く同じコピーを定義しないといけないだろう。それは、行儀の悪いコードに思える。'printValueA'を変更したいときは、すべてのコピーを変更しなければならなくなる。
分かっている。. . . まあ、重要な部分を別のメソッドに分離することはできる、以下のように。
public abstract class Test4BaseStaticMembersClass {
private static String s_valueA = "initial value A";
protected static void printSpecifiedValue (String a_valueName, String a_value) {
System.out.println (String.format ("The value %s is \"%s\".", a_valueName, a_value));
}
public static void printValueA () {
printSpecifiedValue ("A", s_valueA);
}
}
public class Test4ExtendedStaticMembersClass extends Test4BaseStaticMembersClass {
private static String s_valueA = "overwritten value A";
private Test4ExtendedStaticMembersClass () {
}
public static void printValueA () {
printSpecifiedValue ("A", s_valueA);
}
}
public class Test4TestClass {
public void test () {
Test4ExtendedStaticMembersClass.printValueA ();
}
}
ましには見えるが、それでも、すべてのサブクラスで'printValueA'の同一のコピーを作らなければならないのは面倒に思える。
結局、メンバーを上書きする場合、スタティックメンバークラスは最適とはいえない。
それは、不正確な表現だ。上書きされるメンバーがスーパークラス内で参照されて(たいていはメソッドから参照される)いなければ、何の問題もないだろう。
ああ、もしも、's_valueA'が'printValueA'からアクセスされていなければ、シャドーイングをしても、何の問題もないだろう。すると、スーパークラスで参照されているメンバーを上書きする場合、スタティックメンバークラスは最適とはいえない。
そのように思われる。
それでは、どうできるのか?
クラス毎にただ1つの状態を維持するということだけが関心事ならば、別に、スタティックメンバークラスを使うことに固執する必要はない。
それでは何を使うのか?. . . ああ、そうか、シングルトンがある。
もちろん、ある。
以下のようにする。
public abstract class Test5BaseSingletonClass {
private String i_valueA = "initial value A";
public void setValueA (String a_valueA) {
i_valueA = a_valueA;
}
public void printValueA () {
System.out.println (String.format ("The value A is \"%s\".", i_valueA));
}
}
public class Test5ExtendedSingletonClass extends Test5BaseSingletonClass {
public static final Test5ExtendedSingletonClass instance = new Test5ExtendedSingletonClass ();
private Test5ExtendedSingletonClass () {
setValueA ("overwritten value A");
}
}
public class Test5TestClass {
public void test () {
Test5ExtendedSingletonClass.instance.printValueA ();
}
}
こうすれば、シャドーイングをする必要もなく、メソッドをコピーする必要もない。
サブクラスは、自身の単一インスタンスを生成してパブリックスタティックファイナルフィールドとして公開し、コンストラクタはプライベートにして、そのインスタンスのそれ以上の生成を禁止する。