アクセス修飾子

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

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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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

1
2
3
4
5
6
7
8
9
10
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を指定したメソッドはパケージ外の子クラスからでもアクセスできる ことが分かります。

1
2
3
4
5
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を作り、実行してみます。

1
2
3
4
5
6
7
8
9
10
11
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で修飾したメソッドを定義し、別パケージに子クラスを作り、メインクラスで子クラスのインスタンスを作って、そのインスタンス経由で親クラスで定義されたメソッドを利用します。

親クラス

1
2
3
4
5
6
7
8
9
10
11
package there;
 
public class Book {
    private String bookNum;
    public void setbookNum (String bookNum) {
        this.bookNum = bookNum;
    }  
    protected void printlnfo() {
        System.out.println(bookNum);
    }
}

子クラス

1
2
3
4
5
6
package here;
import there.Book;
 
public class Books extends Book{
 
}

メインクラス

1
2
3
4
5
6
7
8
9
10
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:
親クラスは変えずに、子クラスの中でメソッドをオーバーロードして、そのメソッドを同じパケージに置いたメインメソッドの中でアクセスします。

子クラス

1
2
3
4
5
6
7
8
9
10
package here;
import there.Book;
 
public class Books4All extends Book{
     
    protected void printInfo(String str) {
        System.out.print(str);
        printlnfo();
    }
}

メインクラス

1
2
3
4
5
6
7
8
9
10
11
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と同じようにメソッドをオーバーロードして、今度は子クラスとは別のパケージに置いたメインメソッドの中でアクセスします。

メインクラス

1
2
3
4
5
6
7
8
9
10
11
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に変更します。

1
2
3
4
5
6
7
8
9
10
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メソッドか、同じパッケージならデフォルト(パケージプライベート)修飾されたメソッドのみという事が分かります。

カプセル化に戻る