WisdomSoft - for your serial experiences.

5.2 アクセス修飾子

アクセス修飾子を用いることで、クラスのメンバを外部のコードから呼び出すことができるかどうかを設定できます。内部処理用のフィールドやメソッドを非公開にすることで、外部から誤って呼び出されることを防ぐことができます。

5.2.1 クラスのセキュリティ

クラスの開発者は、開発したクラスを利用するのは自分だけではないことを十分考慮しなければなりません。オブジェクト指向プログラミングでは、クラスの利用者(インスタンスを生成してクラスの操作を呼び出すプログラマ)は、クラスが提供するメソッドやフィールドだけを意識します。ですから、クラスの開発者は、常にデータの初期化や開放作業、手続きなどを自動化するように勤めなければなりません。

例えば、RGB 形式の色を表す Color クラスの整数型 r、g、b フィールドに代入可能な値が 0 ~ 255 までという仕様だと仮定したとき、負数や 255 以上の値が代入された時はどうするべきでしょうか?オブジェクト指向プログラミングにおいて、クラス利用者に責任を委譲するべきではありません。この場合、開発者は不正な値が設定されようとした時、それが違法行為であることを知らせる手段を提供するべきです。

だとすれば、クラスの利用者があらゆるフィールドやメソッドにアクセスできるのは危険です。インスタンスが正常に動作するために必要なデータを削除されたり、不正な値を代入されてしまったりする危険性があるのです。これを防ぐためには、クラスのメンバに対してアクセス制限を行うセキュリティを導入する必要があるのです。

Java 言語ではクラスのメンバに、どこまでがアクセス可能かを表すアクセス修飾子を指定することでセキュリティを導入することができます。これによって、同一クラス内からのみを許すメンバ、サブクラスからのアクセスを許すメンバ、どこからでもアクセスを許すメンバなどを指定することができるのです。アクセス修飾子は、どこからでもアクセスすることができる public、サブクラスからのアクセスを許可する protected、外部からのアクセスはすべて遮断する private に分かれます。これらの修飾子は省略することも可能です。これまで何度も書いてきた main() メソッドの public というキーワードは、アクセス修飾子だったのです。

これらのアクセス修飾子は、クラスのメンバに対して指定することができます。構文は static 修飾子と同じで、型の前に記述します。static と異なるのは、コンストラクタにも指定可能であることと、アクセス修飾子を重複して指定することはできないことです。public と static をそれぞれ指定することはできますが、public と private を同時に指定することはできません。

5.2.2 private

private 修飾子を指定されたメンバは、このメンバが宣言されたクラス内からのアクセスのみを許可します。他のクラスや、サブクラスからのアクセスが記述された場合はコンパイル・エラーとなります。

private を指定するべきメンバとは、外部に公開する必要のない内部事情関連のフィールドやメソッドです。例えば、特定の処理をまとめただけのマクロ的な要素が強いメソッドや、自由に値を変更されると都合の悪いフィールドなどです。一般的に、フィールドは外から自由に変更されてしまうと、正常な動作の妨げとなったり、フィールドのチェックをあらゆるメソッドで行わなければならなくなったりと、保守が難しくなるため private を指定して非公開にします。

コード1
class Color {
	private int r , g , b;
	Color(int color) {
		r = (color & 0xFF0000) >>> 16;
		g = (color & 0x00FF00) >>> 8;
		b = (color & 0x0000FF);
	}
	void show() {
		System.out.println("R = " + r + " : G = " + g + " : B = " + b);
	}
}

class Test {
	public static void main(String args[]) {
		Color color = new Color(0xFFAA50);
		color.show();
		//color.r = 100; //エラー
	}
}
実行結果
>java Test
R = 255 : G = 170 : B = 80

コード1の Color クラスのフィールドに private アクセス修飾子が指定されています。この場合、これらのフィールド r、g、b にアクセスできるのは Color クラス内の文だけであり、Test クラスなど、他のクラスからこのフィールドは見えません。Test クラスのコメント化されている文のコメントをはずしてコンパイルすると次のようなコンパイル・エラーが表示されるでしょう。

コード1の r フィールドに外部からアクセスした場合
>javac Test.java
Test.java:17: r は Color で private アクセスされます。
                color.r = 100; //エラー

そのため、クラスの開発者はクラスの利用者から重要なクラスのメンバを隔離し、安全性を確保することができるのです。複雑な処理はクラス内部で隠蔽し、単純な操作だけを提供することがクラス開発の基本であることを心がけましょう。 

5.2.3 デフォルトのアクセス

フィールドやメソッド修飾子は省略することができます。これまでも、クラスを作成する時はメンバの修飾子を省略してきました。アクセス修飾子を省略したメンバのセキュリティはどのようになっているのでしょう。

今までは、アクセス修飾子を省略してきたメンバに他のクラスからアクセスしてきました。そのため、一見するとこのメンバはどこからでもアクセス可能な public であるかのように錯覚します。しかし、そうではありません。Java 言語において、アクセス修飾子を省略した場合は同一パッケージからのアクセスのみを許容すると解釈されています。

ここで、パッケージという新しい用語を使いました。パッケージとは、何らかの機能を提供する総合的なクラスライブラリの空間を指します。例えば、開発者は2次元グラフィックスの機能を提供するパッケージや、ウィンドウシステムの機能を提供するパッケージ、音声制御の機能を提供するパッケージなどを 1 つのシステムとして提供できるのです。通常、パッケージ内のクラス群は同一の開発者、または同一の開発チームによって記述されています。そのため、同一のパッケージ内であれば開放しても、誤った使い方はされないだろうと解釈されるセキュリティレベルです。

とはいえ、デフォルトのアクセスはセキュリティー・ポリシーが曖昧であり、筆者は必ずアクセス修飾子を明記することを強く推奨します。なぜならば、パッケージ内のクラスの開発者や開発チームが同じでも人間は過ちを犯します。内部の動作に密接にかかわるフィールドは private を指定するべきであり、他のクラスからアクセスされるべきではありません。予想外のアクセスによるフィールド情報のクラッシュはバグを生むばかりか、パッケージ内のあちこちで、無秩序なアクセスが存在すれば、クラス間の関係もまとまりがなくなるのです。

パッケージについては、「第6章 クラスの位置」で詳しく説明します。

5.2.4 protected

private が自分自身にのみ公開するものであるのに対し、protected 修飾子は身内にメンバを公開します。クラスの利用者に対して公開する必要はなく、クラスの拡張には必要だと判断されるメンバに対してこの修飾子が用いられます。あなたが開発したクラスは、第三者の手によって継承される可能性があります。このとき、クラスの重要な機能にアクセスできなければ、拡張の範囲を狭めてしまうことになるでしょう。

protected が指定されているメンバにアクセスできるのは、そのメンバを宣言したクラス、そのメンバを宣言したクラスのサブクラス、そして同一パッケージです。メンバを省略した場合と異なり、クラスを継承した第三者がアクセスしてくる可能性もあるということに注意しなければなりません。クラスを公開すれば、当然、他の開発者がクラスを継承することも考えられるのです。そのため、protected を指定するメンバは、拡張に必要な最小限のメンバだけに止めておくべきでしょう。

コード2
class Color {
	protected int r , g , b;
}

class ColorEx extends Color {
	protected int a;
	void setColor(int color) {
		a = (color & 0xFF000000) >>> 24;
		r = (color & 0x00FF0000) >>> 16;
		g = (color & 0x0000FF00) >>> 8;
		b = (color & 0x000000FF);
	}
	void show() {
		System.out.println(
			this + " : A = " + a + " : R = " + r +
			" : G = " + g + " : B = " + b
		);
	}
}

class Test {
	public static void main(String args[]) {
		ColorEx color = new ColorEx();
		color.setColor(0xFFFFAA80);
		color.show();
	}
}
実行結果
>java Test
ColorEx@5224ee : A = 255 : R = 255 : G = 170 : B = 128

コード2では、Color クラスと ColorEx クラスで protected 修飾子をフィールドに指定しています。protected が指定された Color クラスの r、g、b フィールドと、ColorEx クラスの a フィールドはパッケージ外のサブクラスからアクセスすることができます。パッケージを指定していないこのプログラムでは、コード全体が同じパッケージ内にあると判断されるため、protected がなくても他のクラスからもアクセスすることができますが、パッケージの外でこれらのクラスが継承されたとしても、protected を宣言したフィールドにアクセスできることを意識してください。

5.2.5 public

クラスの内部処理の最終的な結果として、利用者に提供するべきメンバには public アクセス修飾子を指定します。public を持つメンバはどのクラスからもアクセスすることができます。クラスが利用者に提供するサービスとは、最終的に public なメンバのことを指します。通常、クラスの開発者は public メンバと protected メンバのみをヘルプに記載します。private とデフォルトアクセスのメンバは開発者以外には関係のない情報なので、ドキュメントに記述するべきではありません。

前に説明したように、多くの場合はフィールドを不特定のタイミングで更新されることを望みません。そこで、メソッドを通じてフィールドとやり取りすることで、安全性の向上を図るのです。これは強制されるものではありませんが、多くの開発者がこの習慣に習っています。

コード3
class Color {
	private int r , g , b;
	Color(int color) {
		r = (color & 0xFF0000) >>> 16;
		g = (color & 0x00FF00) >>> 8;
		b = (color & 0x0000FF);
	}
	public int getR() { return this.r; }
	public int getG() { return this.g; }
	public int getB() { return this.b; }
}

class Test {
	public static void main(String args[]) {
		Color color = new Color(0xFFA050);
		System.out.println(
			"R = " + color.getR() +
			" : G = " + color.getG() + " : B = " + color.getB()
		);
	}
}
実行結果
>java Test
R = 255 : G = 160 : B = 80

コード3の Color クラスは、色情報を保存してる r、g、b フィールドを private に設定しています。そのため、外部からこのフィールドにアクセスすることはできません。その代わり、public を指定した get*() メソッドから赤、緑、青の要素値を取得できるような仕組みをとっているのです。この場合、クラスの利用者は色情報を変更することはできません。つまり、読み取り専用のクラスであると考えられます。参照型はメソッドなどから間接的に値を変えられる可能性もあるため、このような簡単な構造の情報は、読み取り専用にすることで不正な操作を避けるように設計する開発者も少なくありません。

同様に set*() メソッドを作成してフィールドに間接的に値を設定できるようにもできます。そうすれば、フィールドに代入される値が正しいものかどうかをメソッドで調べてから代入することができるのです。これは、クラスの安全性を向上させる効果的な手段です。多くのプログラマは、フィールドを未公開にし、メソッドからフィールドにアクセスするような構造を採用します。このときの get*() メソッドや set*() メソッドの事をアクセッサと呼びます。

5.2.6 未公開コンストラクタ

アクセス修飾子はコンストラクタに設定することも可能です。多くのコンストラクタは public 修飾子を指定し、どこからでも呼び出すことができるように宣言されますが、中には、コンストラクタに protected や private を指定して、インスタンスの生成を拒否するクラスもあります。ただし、コンストラクタに指定できる修飾子はアクセス修飾子だけで、static など、その他の修飾子を指定することはできません。

インスタンスの生成を拒否する理由は様々ですが、主にインスタンスの生成と削除もクラスの開発者が支配したい場合に行います。複雑なシステムになってくると、複数のクラスが複雑な関係を持つことになります。しかし、多対多のネットワークよりは1対多の方がシンプルで保守が容易と考えられるため、それぞれのクラスが、情報を提供する 1 つのインスタンスを参照する構造などが望まれるのです。この場合、複数のインスタンスが作られるとやっかいなのです。

コード4
class SystemInfo {
	private static SystemInfo systemInfo;
	public static SystemInfo getInstance() {
		if (systemInfo == null) {
			systemInfo = new SystemInfo();
			systemInfo.name = "Kitty on your lap";
		}
		return systemInfo;
		
	}

	private String name;
	private SystemInfo() { }

	public String getSystemName() { return this.name; }
}

class Test  {
	public static void main(String args[]) {
		//SystemInfo info = new SystemInfo(); //エラー
		System.out.println(SystemInfo.getInstance().getSystemName());
	}
}
実行結果
>java Test
Kitty on your lap

コード4の SystemInfo クラスのコンストラクタには private アクセス修飾子が指定されています。引数を受け取らないコンストラクタと、オーバーロードされたコンストラクタすべてにアクセス制御をかけた場合、パッケージ外の利用者はインスタンスを生成することができなくなります。

このプログラムでは SystemInfo のコンストラクタが呼び出せない代わりに、SystemInfo オブジェクトを返す getInstance() クラス・メソッドを提供しています。getInstance() メソッドでは、唯一の SystemInfo オブジェクトを保存する systemInfo クラス変数を調べ、null であればまだ生成されていないと判断してインスタンスを生成します。一度 systemInfo クラス変数にインスタンスへの参照を代入すれば、その後は null と等しくならないので、常にこのオブジェクトを返すことになるのです。外部の利用者はこのメソッドを通してこのクラスのオブジェクトを取得することができます。このような構造は、設計の分野でシングルトンと呼ばれています。

静的なメンバを初期化する静的初期化子を持つ Java 言語であれば、このような技術的なデザインを持ちいらなくても、static メンバを提供することで実現することはできますが、その場合は、クラスの機能を使うか使わないかに問わず初期化されてしまいます。しかし、シングルトンであれば必要なときにインスタンス化するため、メモリを無駄遣いすることはないのです。