<<前ページ目次次ページ>>
新発売のコロくん2号
〜継承とコンストラクタ〜

 継承は、汎化だけでなく、再利用にも使えます。おもちゃのロボットの再利用の例で、継承のさまざまな文法事項、主にコンストラクタについて説明します。もちろん汎化のプログラムでも同じ文法事項は当てはまります。

コロくん1号は元祖


 あるおもちゃ会社でロボットを考案しました。その名も「コロくん1号」。なんと「動け」の命令で目盛りひとつ分動きます。ではプログラムを。
[Main.java]
public class Main {
	public static void main(String[] args) {
		Koro1 x;
				
		System.out.println("コロ");
		x = new Koro1();
		x.move();
		x.move();
		x.move();
	}
}
 コロくん1号はクラスKoro1として定義されています。まずオブジェクトを作り変数xに代入します。そしてメソッドmoveを3回呼んでいます。

 出力結果が次のようになるように、クラスKoro1を作りましょう。

   コロ              ←コロくん1号に付けた名前
   コロッ Koro1:move x=1  ←「コロッ」はコロが一目盛り進んだ音、進むたび位置を出力
   コロッ Koro1:move x=2
   コロッ Koro1:move x=3

 この後複数のオブジェクトが登場するので、ここで作ったロボットの名前はコロにします。クラスKoro1でオブジェクトの名前(mainで出力している「コロ」)もインスタンス変数で持てばいいのですが、なるべく単純にするためにやめました。

 もう一度確認しておきますが、「コロくん1号」は製品名で、オブジェクトの名前は、買ってきたロボットにつけた名前と考えてください。
 実際にコロを目の前に出したいのですが、ここは擬似プログラムでご勘弁。動いたという動作を「コロッ」という出力で表すことにします。

 ではクラスKoro1を見てください。ここはまだ本題ではありません。このクラスを継承した新製品を考えるところから、クラスの再利用になります。
[Koro1.java]
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("コロッ");
        }
}

 インスタンス変数は位置の情報xで、コンストラクタで初期値を0にしています。メソッドmoveの中はまず、実際に1目盛り動かすメソッドmoveOnly1Impleを呼びます。先ほど説明したように、この中ではただ「コロッ」と出力するだけで勘弁してもらいます。さらに位置xを1増やし、現在位置を出力します。大丈夫ですか?これを土台に新製品を開発します。


新製品の開発

 コロくん1号が好評につき、グレードアップした新製品「コロくん2号」を開発します。図の上のように、コロくん2号はなんと、いく目盛り動くか指定でき、しかも下の図のようにスタート地点を0以外のところに設定できるという画期的なものです。
 
 せっかくコロくん1号のノウハウがあるので、それを利用しよう、ということで、クラスKoro2はクラスKoro1のサブクラスとして定義することにしました。

[Koro2.java]
public class Koro2 extends Koro1 {
        public Koro2() { //@
                super();
        }
        
        public Koro2(int dx) { //A
                super();
                move(dx);
        }
        
        public void move(int dx) {
                for(int i = 0; i < dx; i++) {
                        move();
                }
                System.out.println("Koro2:move x=" + getX());
        }
}
 まずコンストラクタが二通りありることに気づきましたか?引数なしの@は0からスタート、引数つきのAは、引数で指定した位置からスタートになります。どちらも最初に「super();」がありますね。これはスーパークラスのコンストラクタの呼び出しになります。なにしろ土台ができてなければ、その上に何も積み上げられませんから、サブクラスのコンストラクタの最初は、必ずスーパークラスのコンストラクタを呼んで土台作りをします。

 大事な点をもうひとつ。これから出てくるスーパークラスということばは、直接継承しているクラスのことです。広い意味では、スーパークラスのスーパークラスの...と離れた継承の関係も含めてスーパークラスといいますが、ここでは狭い意味とします。

 @の0からスタートの場合はスーパークラスKoro1のコンストラクタと同じなので、これでおしまい。Aの方はそこから仮引数のdx分だけ移動します。ここは「x+=dx;」でもかまわないかな?と思った方、いいところに目をつけました。残念でした、できません。クラスKoro1のxはprivate指定になっているので、いくらサブクラスといえどもアクセスできません。コンパイルエラーになってしまいます。さらにgetXはあっても、publicのsetXがないので、ここではメソッドmoveを使って、スタート地点まで動かしています。
 もうひとつAの「super();」は「this();」でもかまいません。これで@のコンストラクタを呼び出し、同じことができるからです。
 
 今度はメソッドmoveを見てみましょう。このmoveは引数つきで、いくつ動かすかをここで指定します。クラスKoro1に一目盛り動かすmoveがあるので、これをdx回呼び出せばいいことになります。Koro1の引数なしのmoveを継承しているので、これを呼び出します。最後に位置xを出力しますが、先ほども言ったように、xはprivate指定なので、getXを使って出力します。
 
 では次の命令で、上の図のような感じでゴロとトロが動くことになるのを、出力結果で確かめてください。


 
 「x.move(2);」の呼び出しを見ましょう。クラスKoro2のmoveの中で、2回クラスKoro1のmoveが呼び出されるので、次の2行が出力されます。
   コロッ Koro1:move x=1
   コロッ Koro1:move x=2
 そして最後に位置情報
  Koro2:move x=2
が出力されます。いいですか?

 注目してほしいのは、最後の赤い「x.move();」です。クラスKoro2には引数なしのmoveはありません。しかしスーパークラスの財産を受け継いでいるので、クラスKoro1の引数なしmoveが呼び出されています。


シンプル イズ ベスト?

 慣れてくると、できるだけスマートにプログラミングしたくなります。デフォルト値を使ったり、自動的に挿入される文は省いたり、とすっきりプログラムはかっこいいですね。ではかっこいい(?)形にクラスKoro1とKoro2を直してみましょう。
[Koro1.javaの書き換え]
public class Koro1 {
        private int x;
                                   //←コンストラクタがない
        public int getX() {
                return x;
        }
        
        public void move() {
                moveOnly1Imple();
                x++;               
                System.out.println(" Koro1:move x=" + x);
        }
        
        private void moveOnly1Imple() {
                System.out.print("コロッ");
        }
}
 どこが?といわれそうですが、コンストラクタがなくなっています。それ以外は同じです。実はインスタンス変数は何も代入されないと、次の値が初期値として入ります。

  数値型(byte、short、int、long、float、double) は 0
  boolean は false
  参照型  は null

 nullは特別な参照値で、どのオブジェクトも参照していないことを表します。主にエラーチェックに使用されます。
 インスタンス変数xはint型なので、コンストラクタで0を代入しなくても、0が自動的に初期値になります。

 次はクラスKoro2です。
[Koro2.javaの書き換え]
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()); 
        }
}
 まずコンストラクタでの、スーパークラスKoro1のコンストラクタの呼び出し「super();」がなくなっています。実はコンストラクタの最初が「super(…);」でも「this(…);」でもない場合(引数はあってもなくてもよい)、コンパイラが自動的に「super();」を挿入します。自動的に入れてくれるのは引数なしのコンストラクタなので、スーパークラスに引数なしのコンストラクタがない場合は、コンパイラが困ってエラーを出します。つまりスーパークラスに引数なしのコンストラクタがない場合は、サブクラスのコンストラクタの最初で必ず「super(...);」か「this(...);」を呼び出し、土台を作っておかなければならない、ということです。

 ステップにして2行短くなっています。あまり大勢に影響なさそうですが、他のプログラムを読むときには、自動的にコンパイラがご親切にやってくれることを知っておかないとなりません。


コンストラクタは財産じゃない?

 継承によりスーパークラスの財産を継承することができます。財産とは具体的には、インスタンス変数とメソッドです。これをクラスのメンバといいました。第9章でやりましたね。覚えていますか?コンストラクタはメンバに入りません。ということは、コンストラクタは継承されないのです。

 次の例で見てみましょう。
[Koro1.java]
public class Koro1 {
        private int x;
                
        public Koro1(int dx) {
                for(int i = 0; i < dx; i++) {
                        move();
                } 
        }
           :
}
[Koro2.java]
public class Koro2 extends Koro1 {
        public void move(int dx) {
         :
        }
}
[Main.java]
public class Main {
        public static void main(String[] args) {
                Koro2 y;

                System.out.println("トロ");
                y = new Koro2(2);  //コンストラクタKoro2(int)は未定義です
                y.move();
        }
}

 Koro2のコンストラクタ呼び出し「y = new Koro2(2);」で、そんなコンストラクタはないよ、とエラーになります。クラスKoro1にはint型の引数ひとつのコンストラクタがあるけれど、それはクラスKoro2に継承されるわけではないのです。

 Koro2を次のようにすればOKです。
[Koro2.java修正版]
public class Koro2 extends Koro1 {
        public Koro2(int dx) {
                super(dx);
        }
        
        public void move(int dx) {
          :
        }
}
 ではこうするとどうなるでしょうか?
[Koro2.java]
public class Koro2 extends Koro1 {
        public Koro2(int dx) {
                move(dx);  
        }
        
        public void move(int dx) {
                  :
        }
}

 さっき説明したように、コンストラクタの1行目に「super();」が自動的に挿入されます。ところがクラスKoro1には引数つきのコンストラクタがあるため、引数なしのデフォルトコンストラクタは生成されていません。そこでやはりKoro1にそんなコンストラクタはないよ、とエラーになります。


コンストラクタのまとめ

 あまりにややこしかったと思いますので、ここらでコンストラクタについてまとめておきます。

1.コンストラクタは継承されない。

2.コンストラクタがひとつも定義されていないと、引数なしのデフォルトコンストラクタが自動的に生成される。

3.コンストラクタの1行目は、スーパークラスのコンストラクタ(super(…);)かオーバーロードされた自クラス内のコンストラクタ(this(…); ただしこの中でスーパークラスのコンストラクタが何らかの形で呼び出されていなければならない。)のどちらかでなければならない。もしどちらでもない場合は、コンパイラが1行目に「super();」を自動的に挿入する。
<<前ページ目次次ページ>>
Copyright (c) 2004-2005 Nagi Imai All Rights Reserved.