第6週:アプレットの基本(2)

前回に引き続き、今回も画像を表示するアプレットを中心に学習を進めます。ただし今回は、「アプレットを初期化するinitメソッド」を新たに学び、アプレットが動作するメカニズムを踏まえた上でプログラムを作り直します。また、同じ種類のデータを扱う際に便利な「配列」を用い、複数の画像を表示するアプレットを作成します。

6.1 画像を表示するアプレット(つづき) 
6.2 initメソッドとpaintメソッド、アプレットが動く仕組み 
6.3 複数の画像を扱うアプレットと配列の考え方
6.4 配列の記述の仕方
6.5 配列の添字をうまく利用する
6.6 「ぱらぱらマンガ」を表示するアプレット

6.1 画像を表示するアプレット(つづき)

先週学んだ画像を表示するための手順とプログラムの記述は次の通りでした。

1 表示したい画像を記憶しておく変数を宣言する
Image 変数名;
2 プログラムとは異なるファイルに保存される画像のデータを読み込んで、変数の中に格納・記憶する
img = getImage(getDocumentBase(), "画像ファイルのファイル名");
3 変数の中に保存される画像のデータを表示する
g.drawImage(img,x, y, this); → 但しx,yは整数の値

これら3つの手順をpaintメソッドの定義として書き込めば、ブラウザに画像を表示することができました。ただし実は、このようにpaintメソッドの中にすべてを書き込むやり方は、画像を表示するアプレットの作り方としては一般的ではありません。

ここで新たに登場するのが「initメソッド」です。これはpaintメソッド同様、すべてのアプレットプログラムの親クラスであるAppletクラスに定義されているメソッドです。このメソッドの役割を解説する前に、このメソッドを登場させるとプログラムの記述がどう変わるのかを見てもらいます。先週の「5.6 画像を表示するアプレット」で例示した"「マンボウ」の画像を表示するアプレット"の記述は以下のようになります。見比べてください。

/**
 * 「マンボウ」の画像を表示するアプレット
 * → initメソッドを定義する場合
 **/
 
import java.applet.Applet; 
import java.awt.Graphics;
import java.awt.Image;
public class Manbou2 extends Applet{
	// 画像を記憶する変数を宣言する
	Image img;	
	
	// initメソッド:アプレットの初期化を行うメソッド
	public void init(){
		
		// 画像を読み込む 
		img = getImage(getDocumentBase(), "manbou.gif");
	}
	
	// paintメソッド:アプレット自身の描画を行うメソッド
	public void paint(Graphics g){
		
		// 画像のタイトルを表示する
		g.drawString("Hi, Manbou is my name !", 50, 10);
		// 画像を表示する
		g.drawImage(img, 50, 20, this);
	}
}

→ プログラムの実行結果

initメソッドが加わったことにより、プログラムの記述は先週のものと比べて以下の点で異なっています。

記述がこれだけ変わっている(しかも長くなっている)にもかかわらず、プログラムの動作は全く変わりません。それでも、このようにinitメソッドを用いる方が画像を表示する機能を実現する上ではベターなのです。その理由を次の節で説明します。

6.2 initメソッドとpaintメソッド、アプレットが動く仕組み

先ほどのプログラムで、なぜinitメソッドを定義する方がよいのかを理解するには、アプレットの動作する仕組みとその中でのinitメソッドの役割を理解しなければなりません。そこでまず、アプレットの動作の仕組みを理解しましょう。

アプレットのプログラムはブラウザによって起動され、命令が実行されます。ブラウザがアプレットを実行するプロセスは次の通りです。

1. HTML文書を解釈、appletタグを発見
2. appletタグに記述される「〜.class」という名前のファイルを探す
3. 「〜.class」を見つけたら、そのファイルの中身(=アプレットのプログラム)を読み込む
4. 読み込んだプログラムを解釈、命令を実行する

initメソッドもpaintメソッドも、ともに上記の4の段階の時にブラウザによって探し出され、その中身が実行されます。

ところで、ブラウザがアプレットを実行する際に、呼び出されるメソッドとその順番には決まりがあります。ブラウザは、アプレットには必ずinitやpaintといったメソッドが定義されていること、そしてそれらをいつ呼び出して実行するべきなのかということを、あらかじめ知っているのです。

これはアプレットプログラムが必ずAppletクラスを継承していることと関係しています。Appletクラスにはブラウザに呼び出されるいくつかのメソッドが定義されています。先週学習したとおり、実際にアプレットのプログラムを作る際には、これらのメソッドのうちで中身を書き換える必要のあるものだけを記述し、手を加える必要のないものは何も記述しません。仮にアプレットのプログラムの中身としてpaintメソッドだけが書かれている場合、ブラウザはpaintメソッドを呼び出す際にはアプレットのプログラムに書かれているものを呼び出し、それ以外のメソッドを呼び出す場合には親クラス(Appletクラス)に定義されているものを呼び出します。

というわけで、initメソッドもpaintメソッドもそういったAppletクラスに定義されている、ブラウザから呼び出されて用いられるメソッド群の1部であることが分かりました。それでは、いったいこれらはブラウザからどんなときに呼び出され、どのような役割を持たされているのでしょうか。また、Appletクラスにはこれら以外に、ブラウザから呼び出されるメソッドとしてどのようなものを用意しているのでしょうか。以下の表はそれをまとめたものです(註1)。

public void init() ブラウザがアプレットを読み込んだとき、最初の1回だけ呼び出されます。アプレットのデータの初期化を行いたい場合、これを書き換えます。初期化の際に、1度読み込んだデータは、アプレットが破棄されるまで保持されます。
public void destroy() アプレットがブラウザから破棄されるとき(つまりブラウザが終了するとき)、最後に1回だけ呼び出されます。何らかの後処理が必要な場合だけ書き換えます。
public void start() アプレットがページに表示されるたびに、このメソッドが呼び出されます。この講座の後半で学習する「スレッド」を用いる際に、書き換える必要がでてきます。
public void stop() たとえばユーザーが画面をスクロールしたときなど、アプレットが一時的に表示されなくなったときに、このメソッドが呼び出されます。これも「スレッド」の時に学習します。
public void paint(Graphics g) アプレットがスタートした直後に呼び出され、アプレット自体をブラウザ上に表示します。別のウインドウで隠れていたアプレットが現れたときなど、再描画が必要なときにもその都度呼び出されます。
public void repaint() 描画をし直すことを要求します。このメソッドは書き換えられません。

これらのメソッドの呼び出し順序は、すなわちアプレットの動作順序といえます。すなわち、アプレットは次の模式図のように動いているわけです。

ここまでくると、なぜ画像を表示するアプレットを作る際にinitメソッドを用いる必然性があるのかは、徐々に見えてきたと思います。まとめてみると、以下のような理由によるものです。

また、initメソッドを用いることにより、もう一つ重要な記述の変更が起こりました。先ほど前節の最後に指摘したとおり、画像を記憶する変数(たとえばimgなど)はpaintメソッド及びinitメソッドの定義の外で宣言されるようになった、ということです。この理由は以前タートルグラフィックスの「部品化(メソッドの作成)」を学んだ際に出てきた、「変数のスコープ(有効範囲)」という考え方を憶えていれば分かるはずです。

仮に以下のように変数を一方のメソッドの中で宣言してしまうと、他方のメソッドからはそれにアクセスすることができません。

// initメソッド:アプレットの初期化を行うメソッド
public void init(){

	// 画像を記憶する変数を宣言する
	Image img;
		
	// 画像を読み込む 
	img = getImage(getDocumentBase(), "manbou.gif");
}
	
// paintメソッド:アプレット自身の描画を行うメソッド
public void paint(Graphics g){
		
	// 画像を表示する
	g.drawImage(img, 50, 20, this); ←変数imgはinitメソッドで宣言されているので、
                     ここ(paintメソッドの中)でアクセスすることは
	                  できない。
}

したがって、両方のメソッドの外部で宣言しているわけです。

練習問題 6-1

画像を記憶する変数の位置に気をつけながら、initメソッドを利用した「画像を表示するアプレット Manbou2.java」を各自作成し実行しなさい。

6.3 複数の画像を扱うアプレットと配列の考え方

複数の画像を表示する次のようなアプレットを作る場合、今までの知識で対処するならば、以下のような記述になります(クラス名の宣言やパッケージのimportは略しています)。

→ アプレットの実行結果

// 2つの画像を記憶する変数を宣言する
Image img1, img2;

// initメソッド:アプレットの初期化を行う	
public void init(){

	// それぞれの画像を読み込む
	img1 = getImage(getDocumentBase(), "tatsu.gif");// タツの画像を読み込む	
	img2= getImage(getDocumentBase(), "sango.gif"); // サンゴの画像を読み込む  
}

// paintメソッド:アプレット本体を表示する		
public void paint(Graphics g){

	// タイトルを表示する
	g.drawString("Hi, We are TatsuSango !", 50, 10);

	// 表示位置を右にずらしながら繰り返し画像を表示する
	int i;
	for (i=0 ; i<6; i++){

		// 奇数の時にはタツの表示を行う
		if (i%2 == 0){
			g.drawImage(img1, 50+i*100, 20, this);	// forループをカウントする変数iを用いて
		}                      				// 表示位置をずらしている
		// 偶数の時にはサンゴの表示を行う
		else{
			g.drawImage(img2, 50+i*100, 20, this);
		}
	}
}

上の例では扱う画像の数は2つですが、もしも100枚の画像を扱いたい場合、プログラムの記述はかなり大変です。たとえば変数の宣言もこのように、

Image img1, img2, img3....., img100;

100個分の変数の名前をいちいち書いてやらねばなりません。そこで、ひとつのデータしか記憶できない変数の代わりに、ひとつの名前と100個分の部屋をもった「集合住宅」のような記憶場所を用意することができれば、もっと扱いは楽そうです。

そこで登場するのが配列です。配列は同じ型を持った複数のデータをひとまとめにして記憶することができます。

上記のプログラムでは配列ではなく2つの独立した変数が用いられ、次のようにデータが記憶されています。

img1(変数名)
tatsu.gifのデータ(中身)
img2
sango.gifのデータ

それに対して配列を用いる場合、データは次のように記憶されます。

imgs(配列名)

0(添字)

1
tatsu.gifのデータ(中身) sango.gifのデータ

この配列imgsは二つの部屋を持っています。部屋には0から始まる番号(添字)が割り振られており、記憶されているそれぞれのデータに参照する際の手がかりとなります。配列imgの0番にはtatsu.gifから読み込んだデータが、同じく1番にはsango.gifから読み込んだデータが記憶されているわけです。

変数の中身のデータを利用したい場合には変数名を利用します(たとえばimg1を表示したいのであれば「g.drawImage(img1,...)」、img2ならば「g.drawImage(img2,....)」と記述します)。それに対し、配列内のデータは配列名と添字によって参照したいデータを指示します(imgsの0番、というふうにです)。

それでは次に、実際に配列を用いる場合の記述方法を学習しましょう。

6.4 配列の記述の仕方

配列を使う場合には、変数同様、まず宣言が必要です。宣言の仕方はこの通りです。

型(クラス名) 配列名[];
たとえば6.3で登場したアプレットの場合、変数の宣言の代わりに次のように配列の使用を宣言します。

Image imgs[];

これで配列を使うことは宣言されましたが、このままではいくつの部屋を持つ配列なのか、その個数は決まっていません。そこで、次のような記述で個数を決めます。

配列名 = new 型[個数];

上記のアプレットではこれをinitメソッドの中で行うことにします。initメソッドの中で画像の読み込みを行う直前に、次のように記述します。

imgs = new Image[2];

これで配列の用意ができました。あとは配列のそれぞれの部屋に個々のデータを記憶させます。配列に入っている個々のデータのことを要素と呼びます。配列の要素を参照するには

配列名[添字]
とするのが基本です。添字は必ず0から始まります。たとえば、先ほどのimgsの0番の部屋にtatsu.gifのデータを読み込むには次のような代入文を記述します。

imgs[0] = getImage(getDocumentBase(), "tatsu.gif");

また、imgs[0]の要素(つまり"tatsu.gif"のデータ)を表示したければ次のようにします(ここでは表示位置の座標は仮に(100, 100)とします)。

g.drawImage(imgs[0], 100, 100, this);

練習問題 6-2

先ほど6.3で取り上げた2枚の画像を交互に表示するアプレットを、配列を用いて完成しなさい。

6.5 配列の添字をうまく利用する

配列の添字には、0,1,2,3....の定数だけでなく、変数や変数を用いた(たとえばi+1など)も使うことができます。このことをうまく利用すると大変便利です。たとえば5枚の画像を表示するプログラムは次のように書くことができます。

/**
 *  5つの「海の幸」の画像を表示するアプレット
 **/
 
import java.awt.Graphics;
import java.awt.Image;
import java.applet.Applet; 

public class Uminosachi extends Applet{

	// 画像を記憶する配列を宣言する
	Image imgs[];
	
	// initメソッド:アプレットの初期化を行う
	public void init(){
		
		// 配列の要素数を5つ確保する
		imgs = new Image[5];
		
		// 5つの画像ファイルを順に読み込む
		int i;
		for(i=0; i<5; i++){
			imgs[i] = getImage(getDocumentBase(), "image"+i+".gif");  // ファイル名の部分に注意!
		}
	}
	
	// paintメソッド:アプレット本体を表示する	
	public void paint(Graphics g){
	
		// タイトルを表示する
		g.drawString("Hi, We are Uminosachi !", 20, 10);
		
		// 配列に記憶された画像をひとつずつ表示する
		int i;
		for(i=0; i<5; i++){
			g.drawImage(imgs[i], 20, 20+i*100, this);   // 変数iは0から4まで1ずつ推移する
		}
	}
}

→ プログラムの実行結果

まず最初にpaintメソッドの中に注目してください。

int i;
for(i=0; i<5; i++){
    g.drawImage(imgs[i], 100, 100, this);   // 変数iは0から4まで1ずつ推移する
}

このように、g.drawImage()の引数としてimgs[i]と指定すると、変数iの中身の変化に応じて配列の要素が指定されてゆきます。i=0ならばimgs[0]、i=1ならばimgs[1]です。そのさいに、5個の部屋を持つ配列の添字は0から始まり4までである、ということに注意してください。

次に、前後しますが、initメソッド内の画像の読み込みの際にも次のような記述ができます。

int i;
for(i=0; i<5; i++){
    imgs[i] = getImage(getDocumentBase(), "image"+i+".gif");  // ファイル名の部分に注意!
}

上記は5個の画像ファイルをimgs[0]から順に読み込む命令です。ここでひとつ未習事項が出てきています。読み込むファイル名の部分が

"image"+i+".gif"
というふうになっています。この記述は「文字列image」「変数iの中身」「文字列.gif」のすべてを順に連結した結果を表しています。すなわち、もしもiが0ならばこの部分は「image0.gif」、1なら「image1.gif」を表します。あらかじめ読み込むファイル名を「image0.gif, image1.gif,....image4.gif」と統一しておき、getImageの実引数をこのように「"ファイル名"+変数名+".gif"」としておけば、forループと連動させて読み込みを行うことができます。

6.6 「ぱらぱらマンガ」を表示するアプレット

今週学習した内容を応用して、「ぱらぱらマンガ」を表示するアプレットを作ることができます。本格的な動画は、講座の後半に学習する予定の「スレッド」を用いる必要がありますが、ここではそれを用いずに作ります。

ここでは10こまのぱらぱらマンガを1回(つまり10こまを1サイクル)だけ表示する、次のようなアプレットを目標にします(註2)。

→ ぱらぱらマンガのアプレット

このアプレットは、10枚の画像を用意し、それらをこまの進む順に同じ位置に表示してゆくことでできています(ちなみに画像の大きさはすべて200×200、表示位置は(10, 10)です)。

ここで、このアプレットのプログラムの骨組みを考えてみましょう。必要な手順は今まで作成してきた画像表示アプレットと変わりません。このアプレットの名前をWalkとすると、Walkクラスは初期化のためのinitメソッドと表示のためのpaintメソッドが必要になります。さらに、こまを構成する画像のための配列を宣言しなければなりません。

つぎに、それぞれのメソッドの中身を考えましょう。initメソッドとpaintメソッドでは、それぞれどのような仕事をする必要があるでしょうか。もしも分からなかったら前出の"Uminosachi.java"を参考に考えてください。

これらの順序で考えると、次のようなプログラムの骨格ができあがります。

/**
 *  10こまのぱらぱらマンガを表示するアプレット
 **/
 
import java.awt.Graphics;
import java.awt.Image;
import java.applet.Applet; 

public class Walk extends Applet{

	// 画像を記憶する配列を宣言する

	// initメソッド:アプレットの初期化を行う
	public void init(){

		// 配列の要素数を10個確保する

		// ぱらぱらマンガを構成する10枚の画像ファイルを順に読み込む		

	}
	
	// paintメソッド:アプレット本体を表示する	
	public void paint(Graphics g){
	
		// 画像を1こまずつ繰り返し同じ位置に表示する

	}
}

さて、この骨組みを手がかりにプログラムを作成、、、といきたいところですが、ひとつだけ工夫の必要な箇所があります。それは、paintメソッド内の「画像を1こまずつ繰り返し同じ位置に表示する」のところです。ここは基本的には先ほどの"Uminosachi.java"と同様、for文を使って順に画像を表示すればよいのですが、そのままだとコンピュータの処理速度があまりに速いため、「こま送り」の効果が出ません(一瞬で最後のこまが表示されてしまいます)。そこで、ここではfor文の中で、一回画像を表示する度にその直後で「空(から)ループ」を実行させます。これは何も仕事をしないでただカウントするだけの繰り返し文で、目的はコンピュータに「時間稼ぎ」をさせることにあります。

g.drawImage(...)の次の行に、このような記述を行ってください。

// 空ループで時間を稼ぐ
int t;
for(t=0; t<200000; t++);

このように{}で命令を指定せずにセミコロン(;)だけを直後に書いた場合、コンピュータは変数tが0から200000になるまでひたすら増加式だけを実行します(註3)。こうすることで「1こまの描画 → 時間稼ぎ」という処理の流れが実現します。

練習問題 6-3

ぱらぱらマンガを表示するWalk.javaを完成しなさい。


>> 目次ページへ