<<前ページ目次次ページ>>
みずくさいぞコロくん1号
〜継承とアクセス修飾子〜
 アクセス指定子には、public、何も指定なし、privateの3種類以外に、実はもうひとつあります。ここではコロくん2号の改良型を作ることを例に、残るひとつのアクセス指定子を学習しましょう。
連続移動はかっこいい

 サブクラスはスーパークラスの財産(インスタンス変数とメソッド)を受け継ぐ、というのに、private指定のものにはアクセスできません。というののか継承されません。例えば、クラスKoro2からは、Koro1のインスタンス変数xの値は、メソッドgetX経由でしか見られません。またメソッドmoveOnly1Impleも直接呼び出せません。どちらもprivate指定だからです。「スーパー」といえば「サブ」、「サブ」といえば「スーパー」の間柄なのに、これはみずくさい話です。

 だいたい出力が気に入りません。1回「コロッ」と動くたびに、下の図の黄色い部分のように、クラスKoro1のmoveで位置が出力されます。コロくん2号は、右の赤い線のように「コロッコロッ」とかっこよく連続して動きたいのです。そのためには、moveOnly1Imple を、Koro1のmove経由で呼ぶのではなく、クラスKoro2で直接呼べるようになっていたらよさそうです。

 ではコロくん2号の改良型を作ることにしましょう。
 Koro2から、Koro1のxとmoveOnly1Impleにアクセスできるように、Koro1を書き換えてみましょう。旧Koro1でprivate指定だったxとmoveOnly1Impleをprotected指定に変えただけです。それからサブクラスでインスタンス変数xに直接アクセスできるようになるので、メソッドgetXは不要になります。

[旧Koro1]
public class Koro1 {
        private int x;
                
        public Koro1() {
                x = 0;
        }
        
        public int getX() {
                return x;
        }
        
        public void move() {
                moveOnly1Imple();
                x++;            
                System.out.println(" Koro1:move x=" + x);
        }
        
        private void moveOnly1Imple() {
                System.out.print("コロッ");
        }
}
[新Koro1]
public class Koro1 {
        protected int x;
                
        public Koro1() {
                x = 0;
        }
        
        //public int getX() {
        //      return x;
        //}
        
        public void move() {
                moveOnly1Imple();
                x++;               
                System.out.println(" Koro1:move x=" + x);
        }
        
        protected void moveOnly1Imple() {
                System.out.print("コロッ");
        }
}
 アクセス指定は第10章で、メソッド、インスタンス変数ともに、「public」、「」(何も指定なし)、「private」の三つを指定できるということをやりました。実は「protected」の指定もできるのです。これは同一パッケージ内のクラス、あるいはサブクラスからのアクセスを可能にする指定です。
 Koro2はKoro1のサブクラスですから、この指定の変更で、改良型Koro2は、インスタンス変数xとメソッドmoveOnly1Impleにアクセス可能になります。


調子に乗ったコロくん2号

 さあ、クラスKoro2を改良型に書き換えてみましょう。復習のため従来型のKoro2も載せておきます。

[従来型Koro2]
public class Koro2 extends Koro1 {
        public Koro2() { 
                super();
        }
        
        public Koro2(int dx) { 
                super();
                move(dx);
        }
        
        public void move(int dx) {
                for(int i = 0; i < dx; i++) {
                        move();
                }
                System.out.println("Koro2:move x=" + getX());
        }
}
[改良型Koro2]
public class Koro2 extends Koro1 {
        public Koro2() {
                super();
        }
        
        public Koro2(int dx) {
                super();
                move(dx);
        }
        
        public void move(int dx) {
                for(int i = 0; i < dx; i++) {
                        moveOnly1Imple();                       
                }
                System.out.println("Koro2:move x=" + x);
        }
}
 太字の部分を比べてください。まず従来型ではKoro1のメソッドmoveを呼び出していたのが、直接moveOnly1Impleを呼び出しています。またgetXでKoro1のインスタンス変数xの値を取り出していたのを、直接「x」と参照しています。

 では前と同じMainクラスで実行してみましょう。クラスMainも載せておきます。

[クラスMain]
public class Main {
	public static void main(String[] args) {
		Koro2 x;
	
		System.out.println("ゴロ");
		x = new Koro2();
		x.move(2);
		x.move(1);
		
		System.out.println("トロ");
		x = new Koro2(2);
		x.move();
	}
}
さあ、出力は最初やりたかったように、「コロッコロッ...」と連続して出てくるでしょうか。



 おやおや、赤く囲った値がおかしいですね。どうもxの値が増えていないようです。クラスKoro2でMoveOnly1Impleの使い方を間違えた結果です。ではKoro2をどのように直したらよいのでしょうか?考えてみてください。
                   ・・・・・・・・・・・・・・・・・・・・・・・・・・・・

 わかりましたか。Koro1のmoveOnly1Impleは、実際に1動かすだけで、クラスKoro1の持っている位置情報(インスタンス変数x)の更新まではしてくれないのです。ところが上の改良型Koro2では、繰り返しの中でmoveOnly1Impleを呼ぶだけで、xの値を更新してないため、引数ありのmoveで動かしたときの位置がまったく変化していない、という結果になったのです。次のようにすればOkです。

[改良型Koro2 書き換え]
public class Koro2 extends Koro1 {
        public Koro2() {
                super();
        }
        
        public Koro2(int dx) {
                super();
                move(dx);
        }
        
        public void move(int dx) {
                for(int i = 0; i < dx; i++) {
                        moveOnly1Imple();
                        x++;
                }
                System.out.println("Koro2:move x=" + x);
        }
}


全力で守ってくれる? 

 第10章「よそ村の男には気をつけろ」で、アクセス修飾子「public」「」(なし)「private」をやりました。そのときの図を使って「protected」指定の場合を見てみましょう。

 上のように、同一パッケージ内のクラスからならばアクセス可能、別パッケージのクラスの場合、サブクラスならばアクセス可能ということになります。何も指定しない場合と比べ、別パッケージのサブクラスからアクセス可能である、というところが増えています。赤二重丸のアクセスですね。
 ここではインスタンス変数の図になっていますが、メソッドでも同じことが言えます。

 なんとprotectedは、別パッケージのクラスであっても、サブクラスならば何でもアクセス可能にしてしまうのです。逆にサブクラスでなくても、同じパッケージ内のクラスならばどんなものでもアクセス可能です。「protect」の意味は守る、でも「protected」指定で、「守られているわ」という感じはしませんねぇ。
 
 アクセスの範囲は次の順で広がります。おぼえておきましょう。「なし」は何も指定しないことを表します。

    private → なし → protected → public

ここで他のアクセス修飾子と合わせてもう一度表を載せておきましょう。
private クラス内からのみアクセス可能
なし クラス内および同一パッケージのクラス内からアクセス可能
protected サブクラスおよび同一パッケージのクラス内からアクセス可能
public どこからでもアクセス可能
 せっかく第15章で、コロくん1号のKoro1が、メソッドmoveOnly1Impleを他のクラスに公開せずカプセル化を目指したのに、「protected」騒ぎでそれが壊れてしまいそうでした。改良型コロくん2号はかっこよくすばやく動くかもしれませんが、開発は中止になりました。



広げちゃだめ

 サブクラスのメソッドのアクセス修飾子について、ひとつ注意事項があります。サブクラスでメソッドのオーバーライドする場合、元のメソッドのアクセス指定をより狭める指定はできません。次の例を見てください。

[クラスKoro1]
public class Koro1 {
        private int x;

        public Koro1(int dx) {
                move(dx);
        }

        public int getX() {
                return x;
        }

        public void move(int dx) {
                for (int i = 0; i < dx; i++) {
                        moveOnly1Imple();
                        x++;
                }
                System.out.println(" Koro1:move x=" + x);
        }

        private void moveOnly1Imple() {
                System.out.print("コロッ");
        }
}
[クラスKoro2 アクセス指定の誤り]
public class Koro2 extends Koro1 {
        public Koro2(int dx) {
                super(dx);
        }
        
        protected void move(int dx) {
               :
        }
}
 クラスKoro2のメソッドmoveのシグネチャを見ると、クラスKoro1のmoveをオーバーライドしていることがわかります。ここでKoro1のアクセス指定が「public」であるのに対し、Koro2では「protected」とアクセスを狭める方向の指定をしているためコンパイルエラーになります。先ほどの
    private → なし → protected → public
と逆の方向の指定はできないということに注意してください。

 サブクラスは、スーパークラスの財産を受け継ぎ、その上で独自の機能を持っています。ポリモーフィズムはこれを前提として成り立っています。スーパークラスのメソッドより、サブクラスでオーバーライドしたメソッドの方がアクセスできる範囲が狭くなったのでは、この前提が狂ってきてしまうので、この決まりがあります。
サブクラスを作れないクラス
 「もうせっかくprivateにしたものをprotectedにしろ、って言ったり、使い方を間違えたり、いやんなっちゃった。これ以上俺のサブクラスは作らせないぞ!」とコロくん1号は言いました。クラスに「final」の指定をすることで、サブクラスを作れないようにすることができます。次のKoro2は、Koro1にfinal指定があるので、コンパイルエラーになります。

[クラスKoro1]
final public class Koro1 {
              :
}
[クラスKoro2]
public class Koro2 extends Koro1 {
        :
}
コロくんシリーズの計画はどうも頓挫しそうです。
 またオーバーライドさせたくないメソッドにもfinal指定ができます。次は、Koro2で、Koro1のfinal指定されたメソッドをオーバーライドしているため、コンパイルエラーになります。

[クラスKoro1]
public class Koro1 {
       :        
        final public void move() {
                moveOnly1Imple();
                x++;            
                System.out.println(" Koro1:move x=" + x);
        }
       :
}
[クラスKoro2]
public class Koro2 extends Koro1 {
       :
        public void move() {
             :
        }
}
<<前ページ目次次ページ>>
Copyright (c) 2004-2005 Nagi Imai All Rights Reserved.