アクセス修飾子

Javaのアクセス修飾子には最もアクセス制限の緩いpublicから制限の厳しいprivateまで、以下の4種類があります。

修飾子 UMLの可視性 説明
public + すべてのクラスからアクセス可能
protected # 同じパックージ内のクラス、もしくはパッケージ外のサブクラスからアクセス可能
パケージプライベート(指定なし) ~ 同じパックージ内のクラスからのみアクセス可能
private 同一クラス内からのみアクセス可能

アクセス修飾子はクラス、インターフェース、フィールド、メソッド、コンストラクタに指定して、それぞれに対するアクセス制御を行います。
但し、クラス、インターフェースには、publicとパケージプライベートの2種類です。
これはprotectedでクラスを修飾すれば、同一パケージ内かそのサブクラスからしかアクセスできない事になり、クラスの利用範囲が著しく狭くなります。またprivateでは外部から利用できない事になるからです。但し、例外的に内部(インナー)クラスの宣言時に限ってprivateは指定可能です。
これに対しフィールドやメソッドには、4種類の修飾子全てが指定可能です。

それでは、parentパッケージにpublic指定したParentクラスを作り、そのクラスにpublic、protected、指定なし(パケージプライベート)の3種類の指定をしたメソッドを作り、それらメソッドをパッケージ外に作るChildクラスから呼んでアクセス制限がどのようにかかるか見てみましょう。

parentパッケージにスパークラス(親クラス)としてParent.javaを作り、オーバーロードしたメソッドを三つ作って、それぞれに異なるアクセス修飾子を指定します。

package parent;

public class Parent {
        private String msg;
        public void setmsg(String msg) {
                this.msg = msg;
        }
        public void getmsg() {
                System.out.println(msg);
        }
        protected void getmsg(String msg) {
                System.out.println(msg);
        }
        void getmsg(String nashi, String msg) {
                System.out.println(msg);
        }
}

次にparentパケージ外にParentのサブクラス(子クラス)として作るChild.java

import parent.Parent;

public class Child extends Parent {
        public Child() {
                super.setmsg("子から親のpublicメソッドは呼べる");
                super.getmsg();
                super.getmsg("子から親のprotectedメソッドも呼べる");
                super.getmsg("アクセス修飾子なし", "子から親のアクセス修飾子なしメソッドは呼べない");
        }
}

Childクラスをコンパイルすると、以下のようにChild.javaの8行目、親クラスの「アクセス修飾子なし」のメソッドを呼んでいる所で「メソッドにアクセス出来ない」というコンパイルエラーが出ます。

\>javac Child.java
Child.java:8: エラー: Parentのgetmsg(String,String)はpublicではありません。パッケージ外からはアクセスできません
         super.getmsg(“アクセス修飾子なし”, “子から親のアクセス修飾子なしメソッドは呼べない”);
            ^
エラー1個

>

つまり、 アクセス修飾子なし指定の親クラスのメソッドはパケージ外の子クラスからはアクセスできません

一方、Child.javaの8行目をコメントアウトして再度コンパイルすると、エラーは出ずクラスファイルが出来るので、Childクラスのインスタンスを作るメインクラスChild_Main.javaを作り、実行します。
すると public及びprotectedを指定したメソッドはパケージ外の子クラスからでもアクセスできる ことが分かります。

public class Child_Main {
	public static void main(String args[]) {
		Child cd = new Child();
	}
}
>java Child_Main
子から親のpublicメソッドは呼べる
子から親のprotectedメソッドも呼べる

>

それでは、最後にアクセス修飾なし指定でアクセス可能な場合を調べてみましょう。
アクセス修飾なし指定されたメソッドは同じパッケージ内の別クラスからはアクセスできるはずなので、Parent.Javaと同じparentパケージ内にParent_Main.javaを作り、実行してみます。

package parent;

public class Parent_Main {
	public static void main(String args[]) {
		Parent cd = new Parent();
		cd.setmsg("同じパケージのpublicメソッドは呼べる");
		cd.getmsg();
		cd.getmsg("同じパケージのprotectedメソッドは親子関係がなくても呼べる");
		cd.getmsg("アクセス修飾子なし", "同じパケージのアクセス修飾子なしメソッドは呼べる");
	}
}
>java parent.Parent_Main
同じパケージのpublicメソッドは呼べる
同じパケージのprotectedメソッドは親子関係がなくても呼べる
同じパケージのアクセス修飾子なしメソッドは呼べる

>

このように、4つのアクセス修飾子のうち、 区別が付きにくいのはprotectedとパケージプライベートの違い です。
整理しておくと、protectedなフィールドやメソッドは、パッケージ外のサブクラスからでもアクセスできるのに対し、ぺケージプライベート、つまりアクセス修飾子を指定しなかった場合は、パッケージ外のサブクラスからはアクセスできず、同一パケージ内のクラスからのみアクセスできます。

次に、アクセス修飾子の中でprotectedは他の修飾子と異なり「サブクラスからのアクセスを許す」パケージとは関係のない条件が設定されています。ここで、間違えやすいのはクラスとインスタンスを同列に考えてしまうことです。

例を見てましょう。
親クラスにprotectedで修飾したメソッドを定義し、別パケージに子クラスを作り、メインクラスで子クラスのインスタンスを作って、そのインスタンス経由で親クラスで定義されたメソッドを利用します。

親クラス

package there;

public class Book {
	private String bookNum;
	public void setbookNum (String bookNum) {
		this.bookNum = bookNum;
	}	
	protected void printlnfo() {
		System.out.println(bookNum);
	}
}

子クラス

package here;
import there.Book;

public class Books extends Book{

}

メインクラス

package here;

public class Main {
	public static void main(String[] args) {
		Books favorite = new Books();

		favorite.bookNum("123-4-567-89012-3");
		favorite.printlnfo();
	}
}

コンパイル結果

>javac here\Main.java
here\Main.java:8: エラー: printlnfo()はBookでprotectedアクセスされます
                favorite.printlnfo();
                        ^
エラー1個

>

親クラス、子クラスのコンパイルは通りますがメインクラスのコンパイルで上記のエラーが出ます。
これはメインメソッドの中で子クラスのインスタンス経由メインクラスのprotected修飾されたメソッドにアクセスしているからです。勘違いしやすいのはprotected修飾子は子クラスに対してアクセスを許すのであって、子クラスのインスタンスにアクセスを許しているわけではない点です。
ここが、パッケージに対するアクセス制御ではないprotected修飾子を指定する上での注意です。

では、本来のprotected修飾子の利用法はどうなるのでしょうか、いくつかの例が考えられます。

例1:
親クラスは変えずに、子クラスの中でメソッドをオーバーロードして、そのメソッドを同じパケージに置いたメインメソッドの中でアクセスします。

子クラス

package here;
import there.Book;

public class Books4All extends Book{
	
	protected void printInfo(String str) {
		System.out.print(str);
		printlnfo();
	}
}

メインクラス

package here;
import there.Book;

public class Main4All{
	public static void main(String[] args) {

		Books4All bk4all = new Books4All();
		bk4all.setbookNum("123-4-567-89012-3");
		bk4all.printInfo("ISBN:");
	}
}

実行結果
メインクラスは同じパケージにあるクラスのprotected修飾されたメソッドにアクセスするので、実行することが出来ます。

>java here.Main4All
ISBN:123-4-567-89012-3

>

例2:
親クラスは変えずに、子クラスは例1と同じようにメソッドをオーバーロードして、今度は子クラスとは別のパケージに置いたメインメソッドの中でアクセスします。

メインクラス

package there;
import here.Books4All;

public class Main4All {
	public static void main(String[] args) {

		Books4All bk4all = new Books4All();
		bk4all.setbookNum("123-4-567-89012-3");
		bk4all.printInfo("ISBN:");
	}
}

コンパイル結果
protected修飾されたメソッドをインスタンスの中でアクセスするので最初と同じコンパイルエラーが出ます。

>javac there\Main4All.java
there\Main4All.java:9: エラー: printInfo(String)はBooks4Allでprotectedアクセスされます
                bk4all.printInfo("ISBN:");
                      ^
エラー1個

>

例3:
例2のコンパイルエラーを無くすためには、子クラスのメソッドのアクセス修飾子をpublicに変更します。

package here;
import there.Book;

public class Books4All extends Book{
	
	public void printInfo(String str) {
		System.out.print(str);
		printlnfo();
	}
}

実行結果

>java there.Main4All
ISBN:123-4-567-89012-3

>

以上の事から、アクセス修飾子でアクセス制御できるのはクラスであってインスタンスではありません。
インスタンスからアクセス出来るメソッドは、同一クラス内を除けばpublic修飾されたクラスのpublicメソッドか、同じパッケージならデフォルト(パケージプライベート)修飾されたメソッドのみという事が分かります。

カプセル化に戻る

パケージ

パッケージの役割は数多くのプログラムをある単位でまとめ、プログラム群を区分けするため、と一般には説明されています。
確かにパケージは、

特定のOSに依存しない名前空間を提供し、実行時の名前の衝突を避ける
Javaファイル、classファイルの分類を可能にする。

という機能があります。

しかしそれだけではなく、

アクセス修飾子と組む合わせてアクセス制御機能を提供する。

役割も果たしています。

アクセス修飾子はクラスファイルを他のクラスファイルからアクセス可能にするか否かを提供しますが、
アクセス修飾子だけでは、

すべてのクラスからアクセス可能。
同一クラス内からのみアクセス可能(自分以外のクラス外からはアクセス禁止)。

の2者択一しか設定できません。

しかし、アクセス修飾子をパッケージと組み合わせることにより、さらに
同じパックージ内のクラスからアクセス可能(別パケージからはアクセス禁止)。
という設定が可能になります。

同一パケージ内のクラス群は、一般的には同一目的で同一時期に開発されるプログラム群(=プログラム部品)と考えられるので、この設定は同一目的のプログラム間ではアクセス可能で、同一目的を持たないプログラム群からはアクセスできないという事になります。
つまり、プログラム部品をブラックボックス化するため、アクセスを目的が同じプログラム部品を構成する同一パッケージ内のクラスに制限することが出来ます。

カプセル化に戻る

setterとgetter

一般的に、classのフィールドのアクセス修飾子設定はprivateにし、フィールドは外部に対し隠蔽すべきとされています。
これは、C言語などで変数が簡単に外部のプログラムによって値を壊されてしまう経験から推奨されていることです。

しかし、フィールドのアクセス修飾子をprivateに設定したからといって、同じクラスからはアクセスできるので完全に安全というわけではありません。不注意で自分自身が変数の値を壊してしまうことは起こりえることだからです。

そこで、アクセス修飾子だけに頼るのではなく、プログラム的に値の変更の正当性、妥当性をチェックして設計時の意図に反する値の変更を避けることが出来るように、setter、getterメソッドを経由してのみフィールドにアクセスするのが熟練の技、ベストプラクティスです。

setterは名前の通りフィールドの値を設定するメソッドで、getterはフィールドの値を取出すメソッドです。
通常はsetterもgetterもアクセス修飾子はpublicで設定し、全てのクラスからアクセス可能にしますが、フィールドの値変更を特定のクラスのみに限りたい場合は、getterのみpublicに設定し、setterをデフォルト又はprotectedに設定する場合もあります。

なお、フィールドの変数の数が多くなるとsetterとgetterのコーディング自体が面倒だったり、タイプミスをしたりします。
これらの対策としてEclipseでは、メンバ変数をカーソルで選択し[ソース]-[getterおよびsetterの生成]で自動的にsetter、getterのコードを生成する事が出来ます。

カプセル化に戻る