良いコ-ドへの道―普通のプログラマのためのステップアップガイド

第3回スコープを意識したプログラミング―その4 クラス/パッケージのスコープ

クラスのスコープ

Javaではクラスのスコープもprivate、package private、protected、publicの4種類です。

クラスの宣言では主にpublicを使用する場合が多いです。

フレームワークやライブラリを作成する場面ではpackage privateを使用することがときどきあります。ライブラリの利用者に知らせたくないクラスをpackage privateとすることで、のちのちクラスを変更しても変更の影響を利用者に与えないようにすることができます。例を見てみましょう。図2はWebアプリケーションフレームワークCubbyのルーティング機能パッケージ(org.seasar.cubby.routing.impl.*)のクラス一覧です。ClassCollectorとInternalForwardInfoImplはライブラリ利用者が知る必要のないクラスですので、package privateで定義されています。この2つのクラスは実際には同じパッケージのPathResolverImplからのみ利用されます。このようにpackage privateを使用することで、クラスのスコープを小さくし、ライブラリやフレームワークが外部に公開するクラスをコントロールすることができます。

図2 Cubbyのルーティングパッケージの構成
図2 Cubbyのルーティングパッケージの構成

privateは、主に2つの目的で使用されます。1つ目は、インスタンスの生成をそのクラスからのみ行わせたい場合です。デザインパターンのシングルトンパターン[1]などがこれにあたります。ただしメインのクラス宣言はprivateで宣言できませんので、コンストラクタをprivateで宣言して、他のクラスからはインスタンスの生成をできないようにします。2つ目は、そのクラスからのみ利用するインナークラスの定義です(インナークラスについては次項でも解説します⁠⁠。GUIアプリケーションのイベントハンドラ定義などがこれにあたります。

protectedは、インナークラスの宣言でのみ使用できます。インナークラスを下位クラスからのみ利用させたいときに利用します。

インナークラス

インナークラスとは、クラス内に定義されたクラスのことです。インナークラスはほかのクラスの内部で宣言するものですので、通常はprivateで宣言して問題ないはずです。

なお、staticが付かないインナークラスのメソッドからは、インスタンスメソッド同様にフィールド変数にアクセスできますリスト8⁠。

リスト8 インアークラス
public class LoginWindow extends Window {
    private int count = 0;
    ...
    public void init() {
        closeButton.addSelectionListener(new CloseButtonSelectionListener());
    }
    ...
    // インナークラスの宣言
    private class CloseButtonSelectionListener extends SelectionAdapter {
        @Override
        public void widgetSelected(SelectionEvent event) {
            // 外側のクラスのフィールドにアクセス可能...
            System.out.println(count); 
        }
    }
}

無名クラス

無名クラスは匿名クラスとも呼ばれます。無名クラスとは、その場でクラス定義を書いて利用するクラス名がないクラスのことです。

無名クラスはそもそも名前が付かないため、宣言した場所でしか利用できません。これはprivateより小さいスコープになります。1ヵ所でしか使用されることがなく将来的にも再利用することがないクラスは無名クラスとして宣言することで、不測の利用を防ぐことができます。ただし可読性の面から、あえて無名クラスを使わずにインナークラスとして宣言するスタイルが好まれる場合もあります。

Javaの無名クラスのメソッドでは、外側のクラスのフィールドや、final宣言されたローカル変数にアクセスできますリスト9⁠。

RubyのブロックやJavaScriptの無名関数なども、無名クラスと同様に局所的な処理を記述するために利用されます。

リスト9 無名クラス
public class LoginWindow extends Window {
    private int count = 0;
    ...
    public void init() {
        final int result = 10;
        ...
        // 無名クラスの宣言と利用
        closeButton.addSelectionListener(new SelectionAdapter() {
            @Override
            public void widgetSelected(SelectionEvent event) {
                // 外側のクラスのフィールドにアクセス可能...
                System.out.println(count); 
                // final宣言されたローカル変数にアクセス可能
                System.out.println(result); 
            }
        });
    }
    ...
}
Rubyのブロック(Ruby/Tk)
TkButton.new {
  text "Close"
  command { ... }
}
JavaScriptの無名関数
document.getElementById("closeButton").onclick =
function(event) {
  ...
};

キャストを使用した可視性の制御

抽象的な型にキャストすることで、オブジェクトで使えるメソッドを少なくすることができます。

リスト10は変数itemsがArrayListとして宣言されているため、ArrayListの内部のサイズを大きくするensureCapacityメソッドを呼び出すことができます。

リスト10 変数が具象クラスの場合
ArrayList<Item> items = new ArrayList<Item>();
items.ensureCapacity(100); // ArrayListのメソッドを呼び出し

それに対してリスト11では、生成されたArrayListのインスタンスはキャストされ、Listインタフェースのオブジェクトとして変数itemsに保持されます。そのためArrayList固有のensureCapacityメソッドはコンパイルエラーが発生して呼び出すことができません。

リスト11 変数がインタフェースの場合
List<Item> items = new ArrayList<Item>();
items.ensureCapacity(100); // コンパイルエラー

一見すると、リスト10のほうが「より多くのメソッドが呼び出せる」ので便利な気もします。しかし、不要なメソッドは見えないほうが良いのです。リスト11のように、インタフェースのような抽象的な型にキャストすることで、メソッドの一覧にマスクをかけて、不要なメソッドを隠すことができます。これにより、可視性を小さくするとともに、既存のコードを変更することなく、同じインタフェースや親クラスから派生した別の型のオブジェクトに変更できるようになります。

ArrayListの継承関係とpublicメソッドの数を図3にまとめました。ArrayListのpublicメソッドは41個もありますが、Collectionなら22個です。もしもCollectionインタフェースのメソッドしか呼ばないのであれば、リスト11のListよりもCollectionで宣言したほうが、よりオブジェクトで使えるメソッドを少なくできます。

図3 ArrayListの継承関係
図3 ArrayListの継承関係

このように、インタフェースには柔軟性を高める機能のほかに、オブジェクトに制限を加えて可視性を小さくするという側面があるのです。

おわりに

今回は、スコープを意識したプログラミングについてでした。普段なにげなく宣言している変数のスコープや、private/publicなどの可視性も意識してプログラミングできるようになると、コードの見え方も変わってくることでしょう。

達人プログラマ

それでは次回もお楽しみに。

お勧め書籍

『JavaScript 第5版』
David Flanagan(著)、村上 列(訳)
オライリー・ジャパン、2007年
世界で最も誤解されたプログラミング言語JavaScriptを、言語の側面からきちんと説明しているほぼ唯一の書籍です。JavaScriptのスコープについても詳しく解説されています。

おすすめ記事

記事・ニュース一覧