| ホーム > 技術情報 > 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)
他にもバリエーションがありますけれど、まずはここまで。
あなたのご意見・感想をお送りください。 あなたの一言が大きなはげみとなりますので、どんなことでもどうぞ。