page9(update:2016/12/06) 例題9a 例題9b 例題9c 例題9d 


[ prev | next | index ]

9. 継承と実装

目次

9.1 継承の利用

GUIのような複雑な仕組みで動くシステムであっても、継承を利用することで容易に作りかえることができます。私は、このことがオブジェクト指向言語が普及した 一番の原因だと思います。

9.1.1 継承の子のインスタンスは親のインスタンスの代わりに使える

(継承元)を継承した(継承先)には親に存在し たフィールドやメソッドに対応するものが必ず存在します。そこで、

java言語では親クラスを参照する変数に、子クラスの参照を代入可能

と しています。

継承では(継承元)のメンバを上書きできても消すことはできません。従って(継承先)クラスのインスタンスは必ず親のメンバ に対応するものを持っています。子や孫のオブジェクトを親のオブジェクトと見なしてメソッドを呼んだり、フィールドを参照しても、必ず対応するものがあります。

例えば下のようなクラスの継承階層があるとき、

次のようなプログラムを書くことができます。

例題9a

import java.util.Date;
/*
クラス名やメンバ名を日本語名にしてみました。
WindowsXPとjdk1.6の組み合わせではコンパイルと実行が可能です
*/
abstract class 哺乳類 {//abstractは抽象クラスであることを示す。説明は後述
    private Date 誕生日=new Date();//インスタンスが作られた時刻を記録
    public long 年齢() 
    {
        long a=System.currentTimeMillis()-誕生日.getTime();//単位はミリ秒
        return a;
    }
    abstract public void 鳴く();//abstractは抽象メソッドであることを示す。 鳴き声不明で実装できない
}

class 犬 extends 哺乳類
{
    @Override //Java5から使えるようになった アノテーション(annotation) この行は無くてもOK
    public void 鳴く()
    {//上書きしています
        System.out.println("ワンワン");
    }
}

class 猫 extends 哺乳類
{
    @Override //このアノテーションは継承親のメソッドを上書きしていることを明示するためのもの
    public void 鳴く()
    {//上書きしています
        System.out.println("ニャーニャー");
    }
}

public class Test
{
    static public void main(String args[]){
        哺乳類 a=new 犬();//犬インスタンスの参照を哺乳類を参照する変数に代入
        哺乳類 b=new 猫();

        a.鳴く();//※上書きしたメンバが優先される実際に呼び出されるのは犬の「鳴く()」
        System.out.printf("%sの年齢は%dms\n",a.toString(),a.年齢());

        b.鳴く();// 呼び出されるのは猫の「鳴く()」
        System.out.printf("%sの年齢は%dms\n",b.toString(),b.年齢());
    }
}
/*---------実行結果----------
>java Test
ワンワン
犬@1b67f74の年齢は0ms
ニャーニャー
猫@19b49e6の年齢は47ms
*/

ここで、子が親のメンバを同じ名前で上書きしている場合は上書きしたメンバが優先する点が重要です。

※java5からアノテーションを記述できるようになりました。関連する情報を注釈として付け加えるためのものです。javaのソースコードを処理するコンパイラ等のプログラムに対して、付加的な情報を与えるために使えます。

9.1.2 抽象クラス

継承を使うことで良く似たクラスを複数作るときに、先ず、同じ部分を抜き出した汎化クラスを作り、これを継承して実際に利用するクラスを作ることが可能で す。 しかし、同じ部分を抜き出した汎化クラスでは具体的に中身が決められないメソッドも出てきます。例えば上の例では哺乳類の鳴き声は決まらないので、関数本体の実装を行うことができません。

抽象メソッドは名前や引数は決まっているが実装が無いメソッドです。仮に、このクラスのインスタンスが在るとした場合、抽象メソッドを呼ぶと実行できない不具合が生じます。このような抽象メソッドを含むクラスを、java言語ではインスタンスを作れない抽象クラスとします。抽象メソッド、抽象クラスともに修飾子abstractを付けて明示します。

抽象クラスはインスタンスを作ることは出来ません。しかし、継承して使うことが前提なのでこれでかまわないのです。作りたいインスタンスは抽象クラスのインスタンスではなくて継承した子クラスのインスタンスです。子クラスで抽象メソッドを上書きして実装すれば子クラスはインスタンスを作れるようになります。上の例題でも犬と猫のインスタンスは必要ですが哺乳類のインスタンスは不要です。

9.1.3 多態性

継承親を参照する変数に継承の子や孫の参照を代入できます。 異なるクラスのインスタンスが抽象度の高い視点からは同じ物(継承親クラスのインスタンス)として扱えるということです。

メンバの上書きや追加により子や孫 のインスタンスは親のインスタンスとは異なります。しかし、親を参照する変数を使ってメンバを呼び出したとき、参照先の子や孫についても親のメンバ部分が呼び出されたのでは子や孫が親と同じ振る舞いしかできません。そこで、java言語では上書きされていれば、上書きした方のメンバを呼び出す仕組みになっています。

多態性とは同じ親クラスのインスタンスとして扱っているのに、具体的なメンバの呼び出しではインスタンスの実クラスに応じ上書きされた多様な振る舞いをする性質です。

※例題9aでは、哺乳類クラスを作り、犬や猫を哺乳類を継承した子クラスとして作りました。この結果、哺乳類型の変数に具体的な犬、猫の参照を代入できます。ここで、哺乳類型変数のメソッド「鳴く()」を呼び出した場合、メソッドが子クラスで上書きされていれば、それぞれに違う答 えが戻ります。

9.1.4 クラスライブラリ

皆が良く使う機能を毎回プログラミングしていては手間が大変で、大きなプログラムなど作ることはできません。そこで、オブジェクト指向言語の多くが必要と思われるクラスをあらかじめライブラリーとして用意しています。java言語は標準で豊富なライブラリーが用意されています。

 java言語は有用なデータ構造、GUI、通信といった分野の多くのクラスライブラリを用意しています。

このように、プログラマーが必要とする部品をライブラリーとして用意することは普通に行われることです。C言語でもprintfの様に良く使われる関数が関数ライブラリーとして用意されています。

しかし、C言語の様な関数のライブラリーではGUIのような複雑な処理を行うときに、多くの関数を正しい順番で呼び出す必要があり、これには関数が用意された意味や動作などGUIを実現する仕組みを熟知しする必要があります。プログラマーがこのような知識を身につけるには多くの学習時間が必要になり、なかなか皆が使えるものにはなりません。

オブジェクト指向言語では関数をオブジェクトにまとめることができます。さらに、初期化を行うコンストラクタなどで他の部品との連携の基礎的な部分を、あらかじめプログラムしておくこと が可能です。この結果、プログラマーはGUIを実現している細かい仕組みを知らなくても、クラスライブラリーの中のウインドウやボタンなどのクラスを使ってGUIを利用したプログラムが作れましす。

しかし、沢山の部品を用意しても、利用者の多様な要求を全て満たすことは不可能です。ここで、一から自分の部品クラスを作ろうとすると、システムとして動作 ささせる仕組みの学習 が必要になり多くの時間を取られてしまいます。

ここで、継承の仕組みを利用すれば、全てを知る必要は無くなり、変えたいところを実行しているのがどの部分かを知れば足りるようになります。

例題9b ウインドウに文字を表示したい

GUIを使った最初の例題では既存の部品(FrameとLabel)を組み合わせて文字列を表示しました。今回は、Frameクラスを継承し画面の描画を行っているpaintメソッドを上書きして、文字表示を行うMyFrameクラスを作 ります。

※ここでは文字表示しかしていませんが、paintの中で線を引いたり、画像を表示したりと色々なことが可能なのでLabelを貼ることに比べたら多様な描画ができます。

例題ではpaint以外は何も変更しないので、MyFrameには上書きしたpaintメソッドとテスト用のmain関数しか書かれていません。

実行結果はこの様になります


ソースコードは

/*MyFrame.java*/
import java.awt.*;
import java.awt.event.*; /*Frameを継承したMyFrameを作り、Frameとして使う*/ public class MyFrame extends Frame { static public void main(String args[]) { Frame window=new MyFrame();//MyFrameをFrameと見なして使う window.setSize(300,120);//ウインドウの大きさを設定 window.setVisible(true);//ウインドウを表示 } /* * Frameのpaintメソッドを上書き * このメソッドはFrameの画面を描画する為に呼ばれるメソッド */ @Override public void paint(Graphics g) { //描画の色を青に設定 g.setColor(Color.blue); //現在のフォントを取り出し、これから通常書体でサイズが50.0ピクセルのフォントを作り再設定 g.setFont(g.getFont().deriveFont(Font.PLAIN,50.0f)); //文字列の左下が座標(10,80)になる様に描く g.drawString("Hello World",10,80); } }

メモ:継承の根元はjava.lang.Object

用意されたクラスライブラリの継承関係の一部分を下に示します。ここで注意してほしいのは、全ての継承をたどると最後に Objectに達することです。extendsを省いて継承を明示しない場合Objectを継承したと見なされる事は前のページで説明しましたが、結果と して全てのクラスはObjectに始まる継承木の中にあります。
継承の根
java.lang.Object
|-java.awt.image.ColorModel
| |-java.awt.image.DirectColorModel
| |-java.awt.image.IndexColorModel
| |-java.awt.Component GUI部品のクラス
| |-java.awt.Button ボタンのクラス
| |-java.awt.Canvas 絵を描く台紙のクラス
| |-java.awt.Container GUI部品の入れ物クラス
| | |-java.awt.Panel 部品を貼り付けるパネル
| | | |-java.applet.Applet アプレット | | |
| | |-java.awt.Window
| | |-java.awt.Dialog
| | | |-java.awt.FileDialog
| | |
| | |-java.awt.Frame
: : :

Objectのメソッド概要を以下に示します。

Objectは全クラスの一番上の継承親なので、全クラスで以下のメソッドが使えます。

メソッドの概要

javaの継承は単一継承

オブジェクト指向言語のクラス継承の仕組みは言語で多少の違いがあります。C++言語では複数の親クラスを継承できますが、 java言語では親は1個に制限されています。親が複数の継承を多重継承、親が1個の継承を単一継承と呼びます。

多重継承は旨く使えば有効な機能ですが、難しい問題が出てきます。
 例えばクラスAを継承した2つのクラスB,Cがあり、CでAのメソッドA.method()を上書きしていたとします。もし、B,Cを多重継承したクラスDを作 り、Dのインスタンスに対してメソッドmethod()を呼び出すときはAのmethod()を呼ぶかCのmethod()を呼ぶか明示しなければならなくなります。メソッドでなくフィールドでも同じこと が起こります。


目次

9.2 インターフェイス実装の利用

 インターフェイスは戻り値、メソッド名、引数などメソッドの頭部をまとめたクラスみたいなものです。用意されたメソッドの単語セットの様なものと私は解釈し ています。以前の演習で述べた様にこれを実装することで、インターフェイスに用意されたメソッドの存在が保証されます。 従ってクラスの継承と同じく

java言語ではインターフェイス型の参照変数に、
インターフェイスを実装したクラスの参照を代入できます。

従って、クラスの継承と同様にインターフェイスの実装でも多態性が利用できます

9.2.1 インターフェイス

java言語のインターフェイスはインスタンスが外部に提供するメソッドを規定するものです。公開するメソッドをまとめてクラスのように宣言します。ここでメソッドは全て抽象メソッドでなければいけません。

クラスを作るときにインターフェイスの実装を行えば、そのクラスのインスタンスがインターフェイスに規定されたメソッドを全て持つ事が保障されます。そこで、クラスと同様に参照変数の型としてインターフェイスを使うことができ、参照変数に対してインターフェイスに規定されたメソッドを呼び出せます。

インターフェイスには公開の抽象メソッドの他に公開の定数を含めることが許されています。具体的には次の例のように記述しますが、公開であること(public)と、インスタンスが作れないこと(abstract)は当然なのでこれらの記述は省略するのが普通です。

abstract interface Seiseki
{
    //定数を含めることも可能
    public static final int MAX=100;
    public static final int MIN=0;
    
    //抽象メソッドのみ
    public abstract int getTen();//成績の読み出し
    public abstract void setTen(int ten);//成績の記入
    public abstract String getName();//学生の名前
    public abstract int getTanni();//単位数を戻す
}
/*灰色の部分は省略するのが普通*/

インターフェイスではメソッドの実際の処理は何一つ書けません。メソッドの処理部分は、インターフェイスを実装するクラスの側で記述します。どの様に実装するかはクラス側に任されています。

メモ:インターフェイスはクラスと似ている

javaのクラスはインスタンスの構造を規定した分類です。しかし、インスタンスの構造が違っても類似の機能を持つ場合があります。javaのインターフェースは機能による分類と見ることができます。

9.2.2 インターフェイスの実装

javaでは多重継承が許されませんが、人間と日本語話者の両方に分類されるインスタンスを作る必要は無いのでしょうか? こんな時は構造を人間クラスの継承で規定し、日本語を話すといった機能部分をインターフェイス(interface)の実装で規定することが可能です。

インターフェイスの実装はimplementsでクラスの頭部に宣言し、クラスの内部で規定されたメソッドの処理部分を実装します。次の例題では日本語インターフェイスを実装したJapaneseクラスを作ってみました。

main関数に示すようにJapaneseのインスタンス参照はMan型の変数にも日本語型の変数にも代入できます。JapaneseはManにも日本語にも分類できることが判ります。

例題9c

import java.util.Date;

class Man
{
	private Date birthday=new Date();
	public void printBirthday()
	{
		System.out.println("Birthday:"+birthday.toString());
	}

}

/*日本語の部分はインターフェイスとした*/
/*人だけが日本語を話すわけではない、計算機も話す時代になりつつある*/
interface 日本語
{ 
	public void 走る();
	public void 名前();
}


public class Japanese extends Man implements 日本語
{
	String name=null;
	/*コンストラクタ*/
	public Japanese(String name)
	{
		this.name=name;
	}
	
	/*日本語インターフェイスの実装*/
	public void 走る()
	{
		System.out.println("こう見えても疲れまんねん");
	} 
	public void 名前()
	{
		System.out.println("私は"+name);
	}
     
	/*テスト用main*/
	static public void main(String[] args)
	{
		System.out.println("0)Japaneseクラスのインスタンスを生成し");
		Japanese jp=new Japanese("隼人");
		System.out.println();
		
		System.out.println("1)Manクラスのインスタンスとして誕生日を聞く");
		Man m=jp;//親クラス型の変数に代入可能
		m.printBirthday();
		System.out.println();
		
		System.out.println("2)日本語インターフェイスを実装したインスタンスとして命令");
		日本語 j=jp;//インターフェイス型の変数にも代入可能
		j.名前();
		j.走る();
	}
}

実行結果

0)Japaneseクラスのインスタンスを生成し

1)Manクラスのインスタンスとして誕生日を聞く
Birthday:Wed Nov 18 16:35:52 JST 2009

2)日本語インターフェイスを実装したインスタンスとして命令
私は隼人
こう見えても疲れまんねん

インターフェイスでは継承と言わず実装と言います。必ずインターフェイスに規定されたメンバを全てプログラムとして記述し実装しなければなりません。

多重実装が許されています。日本語の単語セットを日本語インターフェイス、英語の単語セットを英語インターフェイスとして用意し、これを人間に同時に実装すれば日英話者 も作れます。

日本語をクラスにしたら?

前の例題で、日本語をクラスにするのは設計として不自然です。どんな問題が起きるでしょうか?

javaは単一継承なので人間をインターフェイスにしなければいけなくなります。人間をインターフェイスで規定しようとすると「考える」のが人間だともいえずできそうにありません。

日本語クラスは作れるでしょうか?人間だけでなく機械でさえ日本語を話します。日本語をはなすための人間と機械に共通する構造を規定するのも無理に思えます。

実装の手間が問題になる場合がある

クラスと同様に インターフェイスは参照変数の型として使え、多態性を生かしたプログラミングが行えます。とても便利な仕組みです。

しかし、インターフェイスは実装するメソッドが多いとプログラムの記述量が増えて大変面倒になります。

例えばWindowの操作に関連したWindowEventを受けとる為のインターフェイス java.awt.event.WindowListenerは次のような7個のメソッドの存在を規定しています。

interface WindowListener
{
        public void windowActivated(WindowEvent e);
        //ウィンドウがユーザのアクティブウィンドウに設定されたときに呼び出されます。

        public void windowClosed(WindowEvent e);
        //ウィンドウに対する dispose の呼び出しの結果として、ウィンドウがクローズされたときに呼び出されます。

        public void windowClosing(WindowEvent e);
        //ユーザが、ウィンドウのシステムメニューでウィンドウを閉じようとしたときに呼び出されます。

        public void windowDeactivated(WindowEvent e);
        //ウィンドウがユーザのアクティブウィンドウではなくなったときに呼び出されます。

        public void windowDeiconified(WindowEvent e);
        //ウィンドウが最小化された状態から通常の状態に変更されたときに呼び出されます。

        public void windowIconified(WindowEvent e);
        //ウィンドウが通常の状態から最小化された状態に変更されたときに呼び出されます。

        public void windowOpened(WindowEvent e);
        //ウィンドウが最初に可視になったときに呼び出されます。
}

これを全て実装するのは大変です。

そこで、何もしない関数で実装したjava.awt.event.WindowAdapterクラ スも用意されています。WindowAdapterを継承すれば自分が上書きしたい部分だけを書換えればいいのでプログラムの手間は軽減されます。

しか し、javaは単一継承しかできないのでWindowAdapterを継承してしまうと他のクラスを継承できなくなってしまいます。

この問題は内部クラスとしてWindowAdapterを継承したクラス を用意し、そのインスタンスに処理を委譲することで回避できます。次の説明を見てください。

多態性を如何にうまく使うかはJavaプログラミングの要点の一つです。
Javaのクラスライブラリを見ると継承と実装が利用されています。これを参考にして研究してみてください。

9.2.3 委譲

委譲とは機能の実現を自分では行わず、他のオブジェクトに丸投げして代行してもらう手法です。ここで、問題は依頼 する側と代行する側の2つのオブジェクトの関係をどのように維持するかです。なぜなら、代行する側は、依頼者のデータを自由に参照できないと仕事ができません。

java言語の仕様には途中から内部クラスの機能が追加されました。これはクラス(外部クラス)の中にクラス(内部クラス)を書けるようにするものです。依頼者クラ スの中に代行者クラスを埋め込むことで、代行者のインスタンスは依頼者のインスタンスのメンバをその名前で自由に参照できます。

内部クラスのインスタンスは外部クラスのインスタンスの内部にあるインスタンスとなります。このため代行者インスタンスを作る際には依頼者インスタンスを指定して、その中に作る必要があります。

次のように内部インスタンスは外部インスタンスを参照する変数からnewを呼び出して生成します。

Outer outer=new Outer();//依頼者側インスタンスの生成
Inner inner=outer.new Inner();//代行者側インスタンスの生成

以下に以前の例題5aにウイン ドウを 閉じる機能を追加する例を示します。内部クラスのインスタンスを生成するnewの使い方に注意してください。

例題9d

/*MyFrame2.java*/
import java.awt.*;
import java.awt.event.*;

public class MyFrame2 extends Frame
{
	static public void main(String args[]) 
	{
		Frame window=new MyFrame2();//MyFrame2の生成に変更
		window.setSize(300,120);
		window.setVisible(true);
	}
	@Override
	public void paint(Graphics g)
	{
		g.setColor(Color.blue);
		g.setFont(g.getFont().deriveFont(Font.PLAIN,50.0f));
		g.drawString("Hello World",10,80);
	}

	//以下が主な追加点
	//コンストラクタで代行者を生成しWindowEventの処理依頼をしておく
	public MyFrame2()
	{
		addWindowListener(this.new Control());
		/*内部クラスのインスタンス生成は上記のように親インスタンスを示してnewを実行する*/
		/*ここでは自分(this)ではなく自分の代行者をリスナーに加えています*/
	}

	/*内部クラス ウインドウを閉じる操作とプログラムの終了を代行する*/
	/*クラスの内側で定義することで 親クラスのメンバを全て参照可能*/
        /*WindowListenerを何もしない関数で実装したWindowAdapterを継承します*/
	class Control extends WindowAdapter
	{            
		//ここでは依頼者MyFrame2のメンバを自由に呼べる	
		@Override
		public void windowClosed(WindowEvent e)
		{
			System.exit(0);//プログラムの終了
		}
		@Override
		public void windowClosing(WindowEvent e)
		{
			dispose();
                        //依頼者MyFrameの継承を遡りWindowクラスのdisposeを呼ぶ。
			//disposeの結果windowClosedが呼ばれる
		}
	}
}

目次

課題 8 (初期ファイルP8.java)

上の例題9cを参考に

を作りなさい。初期ファイルには以下の様なバイリンガルクラスの動作テスト用mainメソッドを作成済みです。

static public void main(String[] args)
{
	Bilingual bi=new Bilingual("隼人","Hayato");
	bi.printBirthday();
	bi.名前();
	bi.走る();
	bi.name();
	bi.run();
	/*--------期待される実行結果はこんな感じ------------
	Birthday:Wed Nov 18 16:35:52 JST 2009
	隼人
	こう見えても疲れまんねん
	Hayato
	I am tired even if I am seen this way
	---------------------------------------------*/
}

目次]

メモ: パッケージについて

javaでは多数のクラスがライブラリとして用意されています。クラスをまとめ分類するものとしてpackageがあります。

(page5でも書きましたが再度)

java言語環境には多くのクラスが標準ライブラリとして用意されています。クラスの数が多いことと名前の衝突を避けるためにク ラスはパッケージ(package)に分けて整理されています。プログラム中でパッケージ内のクラスを利用したい場合、基本的には「パッケージ名.クラス 名」のようにパスを含めて「.」で区切ってフルネームで指定します。ちなみにウィンドウクラスの名前はFrameですが、これは入れ子になったパッケージ の中に置かれていてフルネームはjava.awt.Frameです。

import文

プログラムの中でいちいち長いクラス名を書くのは大変かつ間違いの元ですから、パッケージ名の部分を省略する為にimport文が用意 されています。インポートに続けて書かれた名前のクラスはプログラム中ではフルネームではなくてクラス名のみで使えるようになります。プログラムの先頭に

import java.awt.Frame;

と書いておけばプログラム中ではFrameと書くだけでjava.awt.Frameのことになります。

ここで、使うクラスが多いと面倒なので、全てのクラス名を意味する記号として「*」が使えます。

import java.awt.*;

と書けばjava.awt.全クラスがクラス名のみで使えます。

注意1:Stringクラスも実はjava.lang.Stringなの ですが、import文を書かなくてもStringで使えます。

javaではパッケージjava.lang.のクラスに限ってデフォルトでクラス名のみで使えるように設定されています。つまり

import java.lang.*;

が行われた状態にあるわけです。

 

注意2:[*]を多様するのではなくて、クラス名を明示するのが良い書き方です。
「*」を使いすぎるとクラス名が重複することが有りますから注 意。
   例えば java.util.Date と java.sql.Date 
さらに、
  例えば
  import java.util.*;
  import java.awt.*;
のような複数のインポート分があると、プログラムで使われているVectorクラスがどちらのパッケージにあるか探すのが大変になります。

注意3:import java.awt.*; と書いても java.awt.event.* は含まれません
例えば java.awt.Frame と java.awt.event.WindowEvent をFrameやWindowEventと短く書くた めには
import java.awt.*;
import java.awt.event.*;
の様に2つのインポートを書く必要があります。

static import

他のクラスのメンバを参照する時は

クラス名.メンバ名

とクラス名を付ける必要がありました。しかし、java5からはstaic importを行えばメンバ名だけで参照できるようになっています。Mathクラスの三角関数sin()などはこれまでは

a=Math.sin(Math.PI/4);

のように参照していましたがstaic importを行つて

import static java.lang.Math.*;//staic importの記述

....

a=sin(PI/4);

のように参照することが可能です。


[ prev | next | index ]