ホーム > 技術情報 > Singletonのサブクラス化 | 検索 | 更新情報 |
Javaの入門書 | デザパタ本 | 著書 |
Javaで、Singletonのサブクラス化のコーディングを試みます。インスタンスの唯一性をどのように保証するかを考えましょう。
Singletonパターンのサブクラス化に関連したJavaのサンプルコードを紹介します。
このページを作成するにあたっては、GoF本はもちろんのこと、 デザインパターン・メーリングリストでのメールのやりとりを参考にしました。 澤田聡司さん、小山さん、中野靖治さん、宮本さん、杉村貴士さん、出井秀行さん、 その他のみなさんに感謝します。 特に、澤田さんから多くの情報と示唆をいただきました。感謝します。
(メモ:GoF本のSingletonのバリエーションに言及する必要あり。いつか加筆すること)
メモ:以下のソースではRuntimeExceptionを使用しています。 一般に、RuntimeExceptionの利用には注意が必要ですが、 このソースでRuntimeExceptionをthrowしている部分は「プログラマの誤り」に起因するところであり、 正しい用法のはずです。
JavaでSingletonを作るときの典型的な方法は次の通りです。
例えば、次のようにします。 以下ではDisplayクラスをSingletonとして実現しています。 Displayクラスのインスタンスは唯一で、 Display.getInstance()という式で得ることができます。
class Display { private static Display _instance = new Display(); private Display() { } public static Display getInstance() { return _instance; } public void display(String msg) { System.out.println("Display: " + msg); } } class Main { public static void main(String[] args) { Display obj1 = Display.getInstance(); Display obj2 = Display.getInstance(); if (obj1 == obj2) { System.out.println("obj1 == obj2"); } obj1.display("Hello"); } }
実行結果は次の通りです。
obj1 == obj2 Display: Hello
ただし、上のようなコーディングだと、Displayのサブクラスを作れないという制約が生じます。 コンストラクタをprivateにしているため、 サブクラスを作ると、コンパイル時のエラーになってしまうからです。 かといってコンストラクタをpublicやprotectedにすると、 プログラマが誤ってnewする危険性が生じてしまいます。
次の節では、この制約を解消します。
前節の制約を解消する方法を考えてみましょう。
import java.util.HashMap; class Display { private static HashMap _classnameToInstance = new HashMap(); private static Object _lock = new Object(); protected Display() { synchronized (_lock) { String classname = this.getClass().getName(); if (_classnameToInstance.get(classname) != null) { throw new RuntimeException("Already created: " + classname); } _classnameToInstance.put(classname, this); } } public static Display getInstance(String classname) { synchronized (_lock) { Display obj = (Display)_classnameToInstance.get(classname); if (obj == null) { try { Class cls = Class.forName(classname); obj = (Display)cls.newInstance(); } catch (ClassNotFoundException e) { throw new RuntimeException(classname + " is not found"); } catch (IllegalAccessException e) { throw new RuntimeException(classname + " cannot be accessed."); } catch (InstantiationException e) { throw new RuntimeException(classname + " cannot be instantiated."); } } return obj; } } public void display(String msg) { System.out.println("Display: " + msg); } } class ScreenDisplay extends Display { public void display(String msg) { System.out.println("ScreenDisplay: " + msg); } } class HyperDisplay extends Display { public void display(String msg) { System.out.println("HyperDisplay: " + msg); } } class Main { public static void main(String[] args) { Display screen1 = Display.getInstance("ScreenDisplay"); Display screen2 = Display.getInstance("ScreenDisplay"); Display hyper1 = Display.getInstance("HyperDisplay"); Display hyper2 = Display.getInstance("HyperDisplay"); screen1.display("Hello"); screen2.display("Hello"); hyper1.display("Hello"); hyper2.display("Hello"); if (screen1 == screen2) { System.out.println("screen1 == screen2"); } if (hyper1 == hyper2) { System.out.println("hyper1 == hyper2"); } } }
実行結果は次の通りです。
ScreenDisplay: Hello ScreenDisplay: Hello HyperDisplay: Hello HyperDisplay: Hello screen1 == screen2 hyper1 == hyper2
上のコーディングだと、 Displayクラス・ScreenDisplayクラス・HyperDisplayクラスのそれぞれにインスタンスが1個ずつ存在することになります (正確には0個の場合もありえます。例えば上記のMainクラスを実行したとき、Displayのインスタンスは0個です)。
次の節では、 「クラスごとにインスタンスを1個」確保するのではなく、 「クラス階層全体でインスタンスを1個」する方法を示します。
import java.util.HashMap; class Display { private static Display _instance = null; private static Object _lock = new Object(); protected Display() { synchronized (_lock) { if (_instance != null) { throw new RuntimeException("Already created: " + _instance.getClass().getName()); } _instance = this; } } public static Display getInstance(String classname) throws ClassNotFoundException { synchronized (_lock) { Class cls = Class.forName(classname); if (_instance == null) { try { Display obj = (Display)cls.newInstance(); // assert obj == _instance; } catch (IllegalAccessException e) { throw new RuntimeException(classname + " cannot be accessed."); } catch (InstantiationException e) { throw new RuntimeException(classname + " cannot be instantiated."); } } else if (!_instance.getClass().isAssignableFrom(cls)) { throw new ClassCastException(classname); } return _instance; } } public void display(String msg) { System.out.println("Display: " + msg); } } class ScreenDisplay extends Display { public void display(String msg) { System.out.println("ScreenDisplay: " + msg); } } class HyperDisplay extends Display { public void display(String msg) { System.out.println("HyperDisplay: " + msg); } } class Main { public static void main(String[] args) throws Exception { Display screen1 = Display.getInstance("ScreenDisplay"); Display screen2 = Display.getInstance("ScreenDisplay"); screen1.display("Hello"); screen2.display("Hello"); if (screen1 == screen2) { System.out.println("screen1 == screen2"); } try { Display hyper = Display.getInstance("HyperDisplay"); hyper.display("Hello"); } catch (ClassCastException e) { System.out.println("ClassCastException (OK)"); } } }
実行結果は次の通りです。
ScreenDisplay: Hello ScreenDisplay: Hello screen1 == screen2 ClassCastException (OK)
他にもバリエーションがありますけれど、まずはここまで。
あなたのご意見・感想をお送りください。 あなたの一言が大きなはげみとなりますので、どんなことでもどうぞ。