9.クラスの機能

Copyright(C) 30Sep2013 coskx

クラスには1つのデータの塊を操作するだけでなく,拡張したデータの塊の操作を記述する方法も備わっている。
ここで修得してほしい内容は以下の通りである。
1.クラスの継承。
2.
ポリモフィズム。
3.
抽象クラス
4.
インターフェイス

注意 再利用(汎用)を考えるときは1つのファイルに1つのパブリッククラスをの記述する。
   ここでは,学ぶときの便宜を考え,1つのファイルに複数のクラス等を記述する。

9.1 クラスの継承

クラスを作成してゆくと,似たものクラスを作成することがある。似たものクラスには全く同じメンバ変数やメソッドを含む。先に作ったものを流用し,同じことは書かないようにする方法がある。これはクラスの継承と呼ばれる記述方法を使う。クラスの継承はあまり小さなプログラム例ではご利益がわかりにくいので,少々大きなプログラムをあつかう。

次に取り上げる例では,まず,(9.1.1)にてxy座標を与えるとコンソール画面に座標軸と点xyを表示するからくりを与えるクラスを作る。次に,(9.1.2)で似たようなクラスとして,xy座標を与えるとコンソール画面に座標軸と「点xyと点(0,0)を対角線とする長方形」を表示するからくりを与えるクラスを,継承を利用して作る。

9.1.1 
xy座標を与え,コンソール画面に座標軸と点xyを表示するからくりを与えるクラス
舞台装置としては,コンソール画面用クラスが1つ,コンソール画面用インスタンスが1つ,
点を表すクラスが1つ,点を表すインスタンスが2つ出てくる。
コンソール画面の大きさは横幅76,高さ32で,2つの点は(53,21),(63,14)とする。
座標軸はコンソール画面用インスタンスで用意する。
List 9.1.1 xy座標を与え,コンソール画面に座標軸と点xyを表示する
ファイル名 Chardraw0.java
/* Chardraw0.java */
class Point {
    private int x; /* x座標 */
    private int y; /* y座標 */

    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x; /*受け取ったxを自分自身のxに格納*/
        this.y=y;
    }
    public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            cns.ary[y][x]='*';
        }
    }
}

class ConsoleDisplay {
    int width, height;
    char[][] ary;
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width; /*受け取ったwidthを自分自身のwidthに格納*/
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    /*座標系の舞台装置を2次元配列上に作る*/
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }
    /*受け取ったPointのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(Point mypnt) {
        mypnt.puton(this);
    }
    /*出来上がったコンソールを表示する*/
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

class Chardraw0 {
    public static void main(String args[]) {
        Chardraw0 mainprg = new Chardraw0();
    }
 
    Chardraw0() {
        Point mypoint1 = new Point(53,21);
        Point mypoint2 = new Point(63,14);
        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        mypoint1.print();
        mypoint2.print();
        mycon.setShape(mypoint1);
        mycon.setShape(mypoint2);
        mycon.show();
    }
}


実行結果((53,21),(63,14)に*が表示されている)
>java Chardraw0
Point x=53 y=21
Point x=63 y=14
0----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
|
|
|
|
+
|
|
|
|
1
|
|
|
|                                                              *
+
|
|
|
|
2
|                                                    *
|
|
|
+
|
|
|
|
3
|




クラスPoint内のprivate宣言されたメンバ変数は,クラスChardraw0からアクセスできない。
例えば,変数の初期化以降では,
Chardraw0内のメイン中で,mypoint1.xに直接値を代入したり,
mypoint1.xの値を使うことはできない。

クラスPoint内のprivate宣言されたメンバ変数は,同じクラスPoint内のメソッドからアクセスできる。

また,
クラスConsoleDisplay内のprivate宣言されたメソッドはクラスChardraw0から使うことはできない。
例えば,
Chardraw0内のメイン中で,mycon.fillAry();のような呼び出しはできない。
クラスChardraw0内のprivate宣言されたメソッドは,同じChardraw0内のメソッドからなら呼び出すことができる。


9.1.2
xy座標を与え,コンソール画面に座標軸と「点xyと点(1,1)を対角線とする長方形」を表示する (継承)


xy座標を与え,コンソール画面に座標軸と「点xyと点(1,1)を対角線とする長方形」を表示する
長方形は辺だけを表示するものと,内部を塗りつぶすものを表示する。

ここでは,List 9.1.1で作ったからくりも残し,それを継承して一部変更する方法を用いる。

class PointはList 9.1.1とほぼ同じである。(privateのところがprotectedに変化している。説明は後で)
2つのコンストラクタがあり,メンバ変数はx,y,aryであり,メソッドはfillAry(),print(),
draw(),showAry()が定義されている。

class
Rectangleが定義されているが,class Rectangle extends Pointとなっている。
これは,class Pointを継承していることを表している。

class Rectangleで定義されているのは,2つのコンストラクタとメソッドprint(),
puton(ConsoleDisplay cns)だけである。
class Rectangleに定義されていない
メンバ変数のx,yはclass Pointから継承によって引き継がれており,
class Rectangleに定義されているかのように使うことができる。

メソッド
print(),puton(ConsoleDisplay cns)は再定義されているので,
class
Rectangle内では再定義した方が有効となる。
このように再定義することをメソッドのオーバーライドと呼ぶ,


同様に
class FilledRectangleが定義されているが,class FilledRectangle extends Pointとなっていて,
これもclass Pointを継承していることを表している。

class FilledRectangleで定義されているのは,2つのコンストラクタとメソッドprint(),
puton(ConsoleDisplay cns)だけである。
class FilledRectangleに定義されていないメンバ変数のx,yはclass Pointから継承によって引き継がれており,
class FilledRectangleに定義されているかのように使うことができる。
メソッドprint(),puton(ConsoleDisplay cns)は再定義されているので,
class FilledRectangle内では再定義した方が有効となる。

継承するもとになっているクラスのことをスーパークラス,継承されてできたクラスのことをサブクラスと呼ぶ。
ここでは
class Pointがスーパークラスで,class Rectangleとclass FilledRectangleはサブクラスである。

規則
1)スーパークラスで宣言されたメンバ変数,メソッドのうち,
  サブクラスで宣言されていない
メンバ変数,メソッドは,
  あたかもサブクラス内に定義されているようにふるまう

2)スーパークラスでprotected宣言されたメンバ変数,メソッドは,
  自クラスおよびサブクラスからのみアクセスできる。
  (protect 宣言されたものは同じパッケージ内からもアクセス可能)
  (private宣言されたものは自クラスのみからしかアクセスできない。)
)サブクラス内でスーパークラス
のメソッドと同じ名前のメソッドを記述すると,
  サブクラス内からはスーパークラス内のメソッドではなく,
  自クラスのメソッドが使えるようになる。
  このことをメソッドのオーバーライドと呼ぶ。
  メンバ変数のオーバーライドは厳密にいうとできないと考えるのがよい。
4)同じクラス内で,同じ名前のメソッドで,引数が異なる場合は,メソッドの
  オーバーロードと呼ばれる。(引数が異なれば違うメソッドと認識される。)
5)サブクラスのコンストラクタが呼び出された時には,
  スーパークラスのデフォルトコンストラクタ(引数なしコンストラクタ)
  も自動的に呼び出される。
以下のプログラムもこのことを利用している。
   たとえば,main中で, Rectangle rect= new Rectangle(20,10);は
  class Rectangleのコンストラクタ
Rectangle(int x, int y)を呼び出しているが
  この時,自動的にスーパークラスclass Pointの引数なしコンストラクタが呼び出
  されている。(ここでは意味はない)


List 9.1.2 xy座標を与え,コンソール画面に長方形を表示する複数の継承クラス
ファイル名 
Chardraw1.java
/* Chardraw1.java */
class Point {
   
protected int x; /* x座標 */
   
protected int y; /* y座標 */

    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x; /*受け取ったxを自分自身のxに格納*/
        this.y=y;
    }
    public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            cns.ary[y][x]='*';
        }
    }
}

class Rectangle
extends Point {
    Rectangle() { /*引数なしコンストラクタ*/
    }
    Rectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    public void print() {
        System.out.printf("Rectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        int i;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=x; i++) {
                cns.ary[1][i]='*';
                cns.ary[y][i]='*';
            }
            for (i=1; i<=y; i++) {
                cns.ary[i][1]='*';
                cns.ary[i][x]='*';
            }
        }
    }
}

class FilledRectangle
extends Point {
    FilledRectangle() { /*引数なしコンストラクタ*/
    }
    FilledRectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("FilledRectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        int i,j;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=y; i++) {
                for (j=1; j<=x; j++) {
                    cns.ary[i][j]='*';
                }
            }
        }
    }
}


class ConsoleDisplay {
    int width, height;
    char[][] ary;
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width; /*受け取ったwidthを自分自身のwidthに格納*/
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    /*座標系の舞台装置を2次元配列上に作る*/
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }
    /*受け取ったPointのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(Point mypnt) {
        mypnt.puton(this);
    }
    /*受け取ったRectangleのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(Rectangle myrec) {
        myrec.puton(this);
    }
    /*受け取ったFilledRectangleのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(FilledRectangle myfrec) {
        myfrec.puton(this);
    }
    /*出来上がったコンソールを表示する*/
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

class Chardraw1 {
    public static void main(String args[]) {
        Chardraw1 mainprg = new Chardraw1();
    }
 
    Chardraw1() {
        Point mypoint1 = new Point(53,21);
        Point mypoint2 = new Point(63,14);
        Rectangle myrec1= new Rectangle(43,12);
        Rectangle myrec2= new Rectangle(53,8);
        FilledRectangle myfrec= new FilledRectangle(13,18);
        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        mypoint1.print();
        mypoint2.print();
        myrec1.print();
        myrec2.print();
        myfrec.print();
        mycon.setShape(mypoint1);
        mycon.setShape(mypoint2);
        mycon.setShape(myrec1);
        mycon.setShape(myrec2);
        mycon.setShape(myfrec);
        mycon.show();
    }
}


実行結果
>java Chardraw1
Point x=53 y=21
Point x=63 y=14
Rectangle x=43 y=12
Rectangle x=53 y=8
FilledRectangle x=13 y=18
0----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
|*****************************************************
|*************                             *         *
|*************                             *         *
|*************                             *         *
+*************                             *         *
|*************                             *         *
|*************                             *         *
|*****************************************************
|*************                             *
1*************                             *
|*************                             *
|*******************************************
|*************
|*************                                                 *
+*************
|*************
|*************
|*************
|
2
|                                                    *
|
|
|
+
|
|
|
|
3
|



class FilledRectangleのprint()とputon(ConsoleDisplay cns)の定義のところに,@Overrideが宣言されている。この宣言はなくても構わない。@Override宣言はそのメソッドがスーパークラスの同じシグネチャの関数をオーバーライドしていることを明示している。
このように宣言しておくと,オーバーライドするつもりであったが,タイプミスにより
public void print()

public void priht()
と書いても,オーバーライドするメソッドなのにスーパークラスに
prihtの記述がないのでおかしいとコンパイラが気付いてくれる。もし@Overrideが宣言されていない場合は,prihtというメソッドがそのまま作られるだけで後で困ることになる。
@Override宣言は省略が可能だが,オーバーライドするつもりのメソッドには,書いておいたほうが良いだろう。


参考

「サブクラスのコンストラクタが呼び出された時には,スーパークラスのデフォルトコンストラクタ(引数なしコンストラクタ)
も自動的に呼び出される。」を確かめてみよう。コンストラクタしかないおかしなクラスだが,動作を見るためのテストプログラムである。
MySub sub= new MySub();
の時に,サブクラスのコンストラクタが呼び出されるのは当然だが,その直前にスーパークラスのデフォルトコンストラクタが呼ばれている。また
MySub sub2= new MySub("Hello");
の時にも
スーパークラスのデフォルトコンストラクタが呼ばれてから,サブクラスのコンストラクタが呼び出されているのがわかる。

List 9.1.3 サブクラスのインスタンスの生成時に呼び出されるコンストラクタ
ファイル名 C
onstructor.java
/* Constructor.java */
class MySuper { /*スーパークラス*/
    MySuper() { /*スーパークラスのコンストラクタ*/
        System.out.println("This is MySuper default constructor.");
    }
}

class MySub extends MySuper { /*サブクラス*/
    String str;
    MySub() { /*サブクラスのコンストラクタ*/
        System.out.println("This is MySub default constructor.");
    }
    MySub(String s) {
        System.out.println("This is MySub constructor with String s.");
        str=s;
    }
}

class Constructor {
    public static void main(String args[]) {
        Constructor mainprg = new Constructor();
    }
 
    Constructor() {
        System.out.println("main1");
        MySuper spr= new MySuper();
        System.out.println("main2");
        MySub sub= new MySub();
        System.out.println("main3");
        MySub sub2= new MySub("Hello");
        System.out.println("main4");
    }
}

/******** 実行結果 *********
>java constructor
main1
This is MySuper default constructor.
main2
This is MySuper default constructor.      ←この部分が自動呼出しの証拠
This is MySub default constructor.
main3
This is MySuper default constructor.      ←この部分が自動呼出しの証拠
This is MySub constructor with String s.
main4
****************************/




 課題 9.1

(1)
List 9.1.2にclass FilledRectangle2を追加しなさい。このクラスは,4角形を描くのに,周囲(輪郭)を描くのに使う文字と内部を描くのに使う文字をコンストラクタで選べるようにする。
引数付のコンストラクタは
FilledRectangle2(int ix, int iy, char contour, char area)とし,contourには輪郭を描かせるための半角文字,areaには内部を埋めるための半角文字を与えることとする。
FilledRectangle2 frect2 =new FilledRectangle2(20,10,'*','+');でインスタンスを作り,
frect2.setShape(FilledRectangle myfrec);で輪郭が'*'で,内部が'+'の長方形を描くようにしなさい。


(2)
List 9.1.2では点を1つだけ与えて,点(1,1)と与えた点を対角線とする長方形を作らせていた。
List 9.1.2にclass Rectangle2を追加しなさい。コンストラクタで2点を与えることにして,長方形などを描けるようにしなさい。
例えばクラスRectangle2のコンストラクタは

Rectangle2(int x1, int y1, int x2, int y2)
となり,2点の座標は(x1,y1),(x2,y2)とする。必要ならx1<x2,y1<y2の縛りをいれてもよい。

9.2 インスタンスのキャスト


サブクラスのインスタンスを作成しておきながら,スーパークラスのインスタンス変数に参照を代入するとどうなるかを見てみよう。

List 9.2.1 インスタンスのキャスト クラスの定義部分はList 9.1.2と同じ(薄い色のところ)
ファイル名 
Chardraw2.java
/* Chardraw2.java */
class Point {
    protected int x; /* x座標 */
    protected int y; /* y座標 */

    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x; /*受け取ったxを自分自身のxに格納*/
        this.y=y;
    }
    public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            cns.ary[y][x]='*';
        }
    }
}

class Rectangle extends Point {
    Rectangle() { /*引数なしコンストラクタ*/
    }
    Rectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    public void print() {
        System.out.printf("Rectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        int i;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=x; i++) {
                cns.ary[1][i]='*';
                cns.ary[y][i]='*';
            }
            for (i=1; i<=y; i++) {
                cns.ary[i][1]='*';
                cns.ary[i][x]='*';
            }
        }
    }
}

class FilledRectangle extends Point {
    FilledRectangle() { /*引数なしコンストラクタ*/
    }
    FilledRectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("FilledRectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        int i,j;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=y; i++) {
                for (j=1; j<=x; j++) {
                    cns.ary[i][j]='*';
                }
            }
        }
    }
}

class ConsoleDisplay {
    int width, height;
    char[][] ary;
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width; /*受け取ったwidthを自分自身のwidthに格納*/
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    /*座標系の舞台装置を2次元配列上に作る*/
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }
    /*受け取ったPointのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(Point mypnt) {
        mypnt.puton(this);
    }
    /*受け取ったRectangleのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(Rectangle myrec) {
        myrec.puton(this);
    }
    /*受け取ったFilledRectangleのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    public void setShape(FilledRectangle myfrec) {
        myfrec.puton(this);
    }
    /*出来上がったコンソールを表示する*/
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}


class Chardraw2 {
    public static void main(String args[]) {
        Chardraw2 mainprg = new Chardraw2();
    }
 
    Chardraw2() {
        Point mypoint1 = new Point(53,21);
        Point mypoint2= new Rectangle(43,12);
        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        mypoint1.print();
        mypoint2.print();
        mycon.setShape(mypoint1);
        mycon.setShape(mypoint2);
        mycon.show();
    }
}


Point mypoint1 = new Point(53,21);
では,class 
Pointのインスタンスの参照をclass Pointのインスタンス変数に代入している。

インスタンス変数mypoint1には53と21からなるclass PointのインスタンスのIDが保存される。

class Pointのインスタンス変数名
mypoint1 ID xxxxxxxx *1
  class Pointのインスタンス    (ID xxxxxxxx) 
メンバ変数名
x 53
y 21
*1 IDを知ることができないのでxxxxxxxxと表した。

Point mypoint2= new Rectangle(43,12);
では,class Rectangleのインスタンスの参照をclass Pointのインスタンス変数に代入している。

インスタンス変数mypoint2には43と12からなるclass RectangleのインスタンスのIDが保存される。
class Pointのインスタンス変数名
mypoint2 ID yyyyyyyy *2
  class Rectangleインスタンス    (ID yyyyyyyy)
メンバ変数名
x 43
y 12
*2 IDを知ることができないのでyyyyyyyyと表した。

この状態で
mypoint2.print();
を実行すると,
class Pointとclass Rectangleで,どちらのprint()が動くか??
またmycon.setShape(mypoint2)はclass ConsoleDisplay内のどのmycon.setShape()が動くのか??

実行結果
Point x=53 y=21
Rectangle x=43 y=12
0----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
|*******************************************
|*                                         *
|*                                         *
|*                                         *
+*                                         *
|*                                         *
|*                                         *
|*                                         *
|*                                         *
1*                                         *
|*                                         *
|*******************************************
|
|
+
|
|
|
|
2
|                                                    *
|
|
|
+
|
|
|
|
3
|


この結果のように,mypoint2.print();ではclass Rectangleのprint()が動作した。
また,mycon.setShape(mypoint2);ではclass ConsoleDisplay内のmycon.setShape(Rectangle myrec)が動いたことがわかる。

スーパークラスのインスタンス変数に,サブクラスのインスタンスが保存されている状態で,
メソッドを呼び出すと,インスタンスが属しているサブクラスのメソッドが起動する。


ただし,インスタンス変数が,スーパークラスに属し,インスタンスがサブクラスに属するようには出来るが,
この逆はできるとは限らない。例えば次のようになる。

class Point    : スーパークラス
class Rectangle: サブクラス
○ Point px = new Rectangle(35,12);
×? Rectangle px = new Point(35,12);



9.3 ポリモーフィズム(多態性)


スーパークラスのインスタンス変数に,サブクラスのインスタンスを保存できるので,すべてのサブクラスのインスタンスをスーパークラスのインスタンス変数に保存し,メソッドを呼び出すと,インスタンスに応じたメソッドが自動的に使われるようになる。その例を示そう。


List 9.3.1 インスタンスのキャスト (クラスの定義部分はList 9.1.2と同じ(薄い色のところ)
ファイル名 Chardraw3.java

/* Chardraw3.java */
class Point {
    protected int x; /* x座標 */
    protected int y; /* y座標 */

    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x; /*受け取ったxを自分自身のxに格納*/
        this.y=y;
    }
    public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            cns.ary[y][x]='*';
        }
    }
}

class Rectangle extends Point {
    Rectangle() { /*引数なしコンストラクタ*/
    }
    Rectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    public void print() {
        System.out.printf("Rectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    public void puton(ConsoleDisplay cns) {
        int i;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=x; i++) {
                cns.ary[1][i]='*';
                cns.ary[y][i]='*';
            }
            for (i=1; i<=y; i++) {
                cns.ary[i][1]='*';
                cns.ary[i][x]='*';
            }
        }
    }
}

class FilledRectangle extends Point {
    FilledRectangle() { /*引数なしコンストラクタ*/
    }
    FilledRectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("FilledRectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        int i,j;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=y; i++) {
                for (j=1; j<=x; j++) {
                    cns.ary[i][j]='*';
                }
            }
        }
    }
}

class ConsoleDisplay {
    int width, height;
    char[][] ary;
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width; /*受け取ったwidthを自分自身のwidthに格納*/
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    /*座標系の舞台装置を2次元配列上に作る*/
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }

   
    /*受け取ったPointのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    /*Pointはスーパークラスなので,サブクラスのインスタンスが*/
    /*やってきてもその素性に応じたメソッドputon()を呼び出す*/

    public void setShape(Point mypntObj) {
        mypntObj.puton(this);   /*thisはこのクラス(のインスタンス)*/
    }

    /*出来上がったコンソールを表示する*/
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

class Chardraw3 {
    public static void main(String args[]) {
        Chardraw3 mainprg = new Chardraw3();
    }
 
    Chardraw3() {
        Point[] pnt = new Point[5];
        pnt[0] = new Point(53,21);
        pnt[1] = new Point(63,14);
        pnt[2] = new Rectangle(43,12);
        pnt[3] = new Rectangle(53,8);
        pnt[4] = new FilledRectangle(13,18);

        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        int i;
        for (i=0; i<5;i++) {
            pnt[i].print(); /*pnt[i]の素性に応じたクラスのprint()が呼び出される*/
        }
        for (i=0; i<5;i++) {
            mycon.setShape(pnt[i]);
        }
        mycon.show();
    }
}



実行結果
>java Chardraw3
Point x=53 y=21
Point x=63 y=14
Rectangle x=43 y=12
Rectangle x=53 y=8
FilledRectangle x=13 y=18
0----+----1----+----2----+----3----+----4----+----5----+----6----+----7----+
|*****************************************************
|*************                             *         *
|*************                             *         *
|*************                             *         *
+*************                             *         *
|*************                             *         *
|*************                             *         *
|*****************************************************
|*************                             *
1*************                             *
|*************                             *
|*******************************************
|*************
|*************                                                 *
+*************
|*************
|*************
|*************
|
2
|                                                    *
|
|
|
+
|
|
|
|
3
|


この例のように,共通のメソッド呼び出しで,インスタンスの種類に応じて,あらかじめ決められた別々の処理を行う性質のことは,ポリモーフィズム(多態性)と呼ばれる。

ブルーレイプレーヤにおいて,DVD形式のディスクを入れて,再生ボタンを押すと,DVDが再生され,ブルーレイ形式のディスクを入れて再生ボタンを押す と,ブルーレイが再生されているのと同じである。プラスチックの円盤がスーパークラスであり,DVDまたはブルーレイ形式の信号が載っているディスクがサ ブクラスである。自動的に判別されて,必要な再生機構が選ばれて,再生されるというのがポリモーフィズムである。

9.4 抽象クラス


これまで,class Pointを継承してclass Rectangleやclass FilledRectangleを作ってきた。ところで,この3つのクラスを作ったときは,たまたまclass Pointが先にできたので,残りの2つを継承で作った。class Rectangleが先にできて残りの2つを継承で作ることもできる。このようなときには,3つのクラスの共通部分のみ取り出したクラスを先に作って,そ こから継承で3つのサブクラスを作成できる。このような共通部分のみ取り出したクラスは抽象クラスと呼ばれる。抽象クラスのインスタンスを作ることはできないし,その必要もない。

3つのサブクラス内には作業内容は同じではないが,引数が同じメソッドがある。(上記の例ではprint(),puton())メソッドの中身を抽象クラス内に記述することはできないが,シグネチャ(C言語のプロトタイプ宣言)のみ記述し,サブクラスでオーバーライドする。

抽象クラスはclass定義のところにabstractをつけ,オーバーライドされるべきメソッドにもabstractを付ける。
逆にabstractのついたメソッドはサブクラス中で必ず定義されオーバーライドされなければならない。

次の例では,抽象クラスCoordinateを定義し,class Pointを含む3つのクラスをclass Coordinateから継承して作っている。
class Coordinateがスーパークラスであり,class Point,class Rectangle,class FilledRectangleはサブクラスになる。
そのため,サブクラスのコンストラクタが呼び出された際は,必ずclass Coordinateのコンストラクタが呼び出されることになる。

List 9.4.1 抽象クラスCoordinateの導入
ファイル名 Chardraw4.java
/* Chardraw4.java */
abstract class Coordinate
{
    protected int x; /* x座標 */
    protected int y; /* y座標 */
    abstract public void print();
    abstract public void puton(ConsoleDisplay cns);
}

class Point extends Coordinate {
    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x; /*受け取ったxを自分自身のxに格納*/
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            cns.ary[y][x]='*';
        }
    }
}

class Rectangle
extends Coordinate {
    Rectangle() { /*引数なしコンストラクタ*/
    }
    Rectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("Rectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        int i;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=x; i++) {
                cns.ary[1][i]='*';
                cns.ary[y][i]='*';
            }
            for (i=1; i<=y; i++) {
                cns.ary[i][1]='*';
                cns.ary[i][x]='*';
            }
        }
    }
}

class FilledRectangle
extends Coordinate {
    FilledRectangle() { /*引数なしコンストラクタ*/
    }
    FilledRectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("FilledRectangle x=%d y=%d\n",x,y);
    }
    /*コンソール画面をもらって,その上の自分の座標に'*'を載せる*/
    @Override public void puton(ConsoleDisplay cns) {
        int i,j;
        if (0<x && x<cns.width && 0<y && y<cns.height) {
            for (i=1; i<=y; i++) {
                for (j=1; j<=x; j++) {
                    cns.ary[i][j]='*';
                }
            }
        }
    }
}


class ConsoleDisplay {
    int width, height; /*コンソール表示部の大きさ*/
    char[][] ary;      /**/
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width; /*受け取ったwidthを自分自身のwidthに格納*/
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    /*座標系の舞台装置を2次元配列上に作る*/
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }
    /*受け取ったPointのインスタンスに自分(ConsoleDisplay)の*/
    /*インスタンスを与えて点を載せてもらう*/
    /*Coordinateはスーパークラスなので,サブクラスのインスタンスが*/
    /*やってきてもその素性に応じたメソッドputon()を呼び出す*/
    public void setShape(Coordinate myObj) {
        myObj.puton(this);   /*thisはこのクラス(のインスタンス)*/
    }
    /*出来上がったコンソールを表示する*/
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

class Chardraw4 {
    public static void main(String args[]) {
        Chardraw4 mainprg = new Chardraw4();
    }
 
    Chardraw4() {
        Coordinate[] crd = new Coordinate[5]; //抽象クラスのインスタンスの配列
        crd[0] = new Point(53,21);
        crd[1] = new Point(63,14);
        crd[2] = new Rectangle(43,12);
        crd[3] = new Rectangle(53,8);
        crd[4] = new FilledRectangle(13,18);
        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        int i;
        for (i=0; i<5;i++) {
            crd[i].print(); /*crd[i]の素性に応じたクラスのprint()が呼び出される*/
        }
        for (i=0; i<5;i++) {
            mycon.setShape(crd[i]);
        }
        mycon.show();
    }
}


実行結果は9.3の実行結果と同じになる。





9.5 インターフェイス


9.4で取り上げたプログラムを複数の開発者でシステムを開発する場合,main部分の開発者から見れば,スーパークラスCoordinateのインスタ ンス変数に,サブクラスのPoint,Rectangle,FilledRectangleのインスタンスを代入し,各インスタンス共通のprint() とputon()を使っているだけである。すなわち,print()とputon()の名前の固定と使い方のみクラスの開発者と共有できればよいことにな る。 そこで,2つのものの境界(この場合はスーパークラスCoordinateとそれを使うmainのつなぎ目)の定義して,双方がプログラミングの内容には口を出さないようにする仕組みが作られている。
この仕組みがインターフェイスである。インターフェイスはクラスのように見えるが,メソッドのシグネチャだけが書かれており,メソッドの実現方法は書かれていない。
また,メソッドだけからなるクラスがある場合には,それのスーパークラスになることがある。

インターフェイスは「interface インターフェイス名前」で始まり,メソッドのシグネチャだけが書かれている。
継承では,extends ではなく,implementsと書かれ,「class Xxxxxxxxx implements インターフェイス名前」となる。


List 9.5.1 インターフェイス IF_Shapeの導入
ファイル名 Chardraw5.java
/* Chardraw5.java */
interface
if_Shape {
    abstract public void print();
    /*インスタンスが持っているx,yの値を表示してほしい*/
    abstract public void puton(ConsoleDisplay cns);
    /*座標系の表示とインスタンスの表すべき図形をコンソール座標平面に置く*/
}

abstract class Coordinate implements if_Shape {
    protected int x; /* x座標 */
    protected int y; /* y座標 */
    /*原点は左上とし,右向きにx:正,下向きにy:正*/
    Coordinate() { /*引数なしのコンストラクタ*/
    }
    Coordinate(int x, int y) { /*引数付のコンストラクタ*/
        this.x=x;
        this.y=y;
    }
}

class Point extends Coordinate {
    Point() { /*引数なしコンストラクタ*/
    }
    Point(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("Point x=%d y=%d\n",x,y);
    }
    @Override public void puton(ConsoleDisplay cns) {
        cns.ary[y][x]='*';
    }
}

class Rectangle extends Coordinate {
    Rectangle() { /*引数なしコンストラクタ*/
    }
    Rectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("Rectangle x=%d y=%d\n",x,y);
    }
    @Override public void puton(ConsoleDisplay cns) {
        int i;
        for (i=1; i<=x; i++) {
            cns.ary[1][i]='*';
            cns.ary[y][i]='*';
        }
        for (i=1; i<=y; i++) {
            cns.ary[i][1]='*';
            cns.ary[i][x]='*';
        };
    }
}

class FilledRectangle extends Coordinate {
    FilledRectangle() { /*引数なしコンストラクタ*/
    }
    FilledRectangle(int x, int y) { /*引数付コンストラクタ*/
        this.x=x;
        this.y=y;
    }
    @Override public void print() {
        System.out.printf("FilledRectangle x=%d y=%d\n",x,y);
    }
    @Override public void puton(ConsoleDisplay cns) {
        int i,j;
        for (i=1; i<=y; i++) {
            for (j=1; j<=x; j++) {
                cns.ary[i][j]='*';
            }
        }
    }
}

class ConsoleDisplay {
    int width, height;
    char[][] ary;
    ConsoleDisplay(){
    }
    ConsoleDisplay(int width, int height){
        this.width=width;
        this.height=height;
        ary = new char[height][width];
        fillAry();
    }
    private void fillAry() {
        int i,j;
        char ch;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                ary[i][j]=' ';
            }
        }
        for (j=0; j<width; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='-';
            ary[0][j]=ch;
        }
        for (j=0; j<height; j++) {
            if (j%10==0) ch=(char)(j/10+'0');
            else if (j%5==0) ch='+';
            else ch='|';
            ary[j][0]=ch;
        }
    }
    public void setShape(if_Shape myshape) {
        myshape.puton(this);
    }
    public void show() {
        int i,j;
        for (i=0; i<height; i++) {
            for (j=0; j<width; j++) {
                System.out.print(ary[i][j]);
            }
            System.out.println();
        }
        System.out.println();
    }
}

class Chardraw5 {
    public static void main(String args[]) {
        Chardraw5 mainprg = new Chardraw5();
    }
 
    Chardraw5() {
        if_Shape[] ifcdn= new if_Shape[5];
        ifcdn[0] = new Point(53,21);
        ifcdn[1] = new Point(63,14);
        ifcdn[2] = new Rectangle(43,12);
        ifcdn[3] = new Rectangle(53,8);
        ifcdn[4] = new FilledRectangle(13,18);
        ConsoleDisplay mycon= new ConsoleDisplay(76,32);
        int i;
        for (i=0; i<5;i++) {
            ifcdn[i].print(); /*ifcdn[i]の素性に応じたクラスのprint()が呼び出される*/
        }
        for (i=0; i<5;i++) {
            mycon.setShape(ifcdn[i]);
        }
        mycon.show();
    }
}


実行結果は9.3の実行結果と同じになる。







 課題 9

(1)List 9.1.2にclass FilledRectangle2を追加しなさい。このクラスは,4角形を描くのに,周囲(輪郭)を描くのに使う文字と内部を描くのに使う文字をコンストラクタで選べるようにする。
引数付のコンストラクタは
FilledRectangle2(int ix, int iy, char contour, char area)とし,contourには輪郭を描かせるための半角文字,areaには内部を埋めるための半角文字を与えることとする。
FilledRectangle2 frect2 =new FilledRectangle2(20,10,'*','+');でインスタンスを作り,
frect2.draw();で輪郭が'*'で,内部が'+'の長方形を描くようにしなさい。


(2)
List 9.1.2では点を1つだけ与えて,点(1,1)と与えた点を対角線とする長方形を作らせていた。
List 9.1.2を拡張して,コンストラクタで2点を与えることにして,長方形などを描けるようにしなさい。
例えばクラスRectangleのコンストラクタは

Rectangle(int x1, int y1, int x2, int y2)
となり,2点の座標は(x1,y1),(x2,y2)とする。必要ならx1<x2,y1<y2の縛りをいれてもよい。