Home >> Lecture 7


第7回 プログラムの部品化と課題制作(6/23)


7−1.メソッドの定義の仕方とメソッドの呼出し

プログラムの一部分を意味のあるブロックとして名前を付けることができます。 特定の機能や意味を持たせる場合には、そのような名前付けをした方が良いでしょう。 この名前付けされたブロックのことをオブジェクト指向言語では、「メソッド(Method)」と 呼んでいます。メソッドを利用してプログラムを記述するには、メソッドの2つの側面を覚えておいて下さい。

メソッドは、プログラム中で定義されますが、それだけでは実行されません。 必ず、その定義されたメソッドを呼び出さないと実行がされないのです。 よく、定義だけを書いて、「あれ?何も実行されないよ?」と訝しがる人がいますが、 「呼び出されない限り、実行されない」というメソッドの性格を覚えておけば、そのような 間違いはないと思います。では、定義をする方から見ていきましょう。

■引数のないメソッドの定義と呼び出し

☆引数のないメソッドの定義

メソッドの定義の書式は、繰返しの書式と似ています。ブロックを使いますが、それに 名前をプログラムに導入するという書式と組み合わせたものと考えると良いでしょう。 以下のように記述します。

プログラムの一部を機能として分けながらプログラミングしていくのは、 プログラマにとってもわかりやすいですし、後でどこを変更すれば良いのかがわかりやすくなります。 メソッドの定義は次の書式に従って記述します。 これは、今までアプレットのstartメソッドで行なってきたものと同じです。

▼メソッド定義の書式
 void メソッド名( ) ブロック

メソッド名の後には、必ず丸括弧( )を記述します。 メソッドはこの丸括弧があることによって、変数と区別されています。 返す値がない場合は、voidという型名が使われます。 もしも、メソッドが呼出し時に受け取る情報がなければ、上のように丸括弧内には何も書かなくて構いません。 それでは、例として、今まで予め役割を与えられた既出のメソッドを見てみましょう。


	public  void   start(  )   {    }
		

タートル・グラフィックスで自動的に起動されるstartという名前のメソッドです。 返す値の型がvoidになっています。ですから、このメソッドは何も値を返さないことを示しています。 voidの前にpublicがついています。これは、「外部に公開する」という意味を持っています。 この修飾語がある場合は、オブジェクトの外部からもこのメソッドを利用できるということを示しています。 自分で作るときにはつけなくても構いません。 また、仮引数の宣言(丸括弧の中)の部分には何も記述されていませんので、 呼び出されたときには、何も受け取らないことを示しています。

この上記のメソッド名の部分には、動詞や動詞句などにしても構いませんが、 メソッドの名前は小文字で始めるという不文律があります。 具体的な例を見てみましょう。 四角形(正方形)を描くメソッドを定義しています。

		
	// 正方形を描くメソッド
	void drawSquare( ) {
		Turtle  turtle = new Turtle( this );
		int  n = 1;
		while ( n <= 4 ) {
			turtle.forward( 50 );
			turtle.rotate( 90 );
			n = n + 1;
		}
	}
		

メソッドの定義のブロックの中に、更に繰返しのブロックがネストして入っていますので、 インデント(右への字下げ)が、繰返しの中ではだいぶ深くなっています。 名前をみてもわかるように「drawSquare」というような動詞句(Phrasal Verb)にしても 構いません。動詞句にした場合は、動詞の部分を小文字で始め、複合語になっている名詞の部分を 大文字で始めます。

☆引数のないメソッドの呼出し

メソッドの呼出しは簡単です。次のような書式で記述します。

引数のないメソッドの呼出しは次の書式に従って記述します。

▼メソッド呼出しの書式
 メソッド名( );

丸括弧の後に「;」(セミコロン)が必要なことに注意して下さい。これがJavaでの文の終わりを示しています。 それでは、先ほど定義した「drawSquare」を呼び出して使ってみましょう。 startメソッドの中だけを記述しています。

		
	drawSquare( );
		

プログラミングの世界では、これだけ記述するだけで動いてしまうので、摩訶不思議な 感じがします。丸括弧は、メソッドの呼出しを示しているので、書き忘れないでください。

☆メソッドの定義があるプログラムの書き方

Javaのプログラムでは、自作のメソッドの定義は、以下のようにクラスのブロックの中に記述します。

public class Sample { // クラスのブロックの始まりです。
public static void main( String [ ] args) { new Sample( ); }
public void start( ) { // startメソッドのブロックの始まりです。

} // startメソッドのブロックの終了です。
// その他のメソッドは、この部分に記述します。
} // クラスのブロックの終了です。

タートル・グラフィックスでは、mainメソッドが呼ばれ、その中からstartメソッドが呼ばれるように 設計されています。 ですから、mainやstartメソッド以外のメソッドの定義は、それらの2つのメソッドの記述が終わってから後に 記述するようにしましょう。そう書かなくても動くようには設計されているはずでしょうが、 まずは実行されるメソッドから書いていった方がプログラム全体として何をするのかがわかりやすくなります。 先ほどのdrawSquare( )メソッドを保持したプログラムは、全体として次のように記述します。


	import  sfc.turtle.TurtleFrame;
	import  sfc.turtle.Turtle;
	
	public class DrawHouseTester1 extends TurtleFrame {
		
		public static void main( String [ ] arg ) { new DrawHouseTester1( ); }
		
		public void start( ) {
			drawSquare( );		// drawSquareを呼び出すだけ
		}
		
		// 正方形を描くメソッド
		void drawSquare( ) {
			Turtle  turtle = new Turtle( this );
			int  n = 1;
			while ( n <= 4 ) {
				turtle.forward( 50 );
				turtle.rotate( 90 );
				n = n + 1;
			}
		}
	}
		

☆実習7-1 2つのタートルによる家の描画

家を描画するプログラムを記述してみましょう。基本的には、上記のdrawSquare( )メソッドを持った プログラムを作成します。その次に、同じ一辺の長さで正三角形を描く、drawTriangle( )メソッドを記述して下さい。 そして、プログラム本体の方で、この2つのメソッドを呼び出します。 これらの軌跡で三角形を描くタートルと四角形を描くタートルは別のものになっています。 2つのタートルがそれぞれを描画します。 両方のタートルとも、90度右に向かせてから描くようにします。三角形の方は、反時計回りに三角形を描かせます。 四角形は、時計回りに描くようにします。 クラス名は、Practice0701とします。コンパイルして実行してみなさい。

☆メソッド名とシグネチャ

ここではJavaでの名前識別方法であるシグネチャ(Signature)と呼ばれる規則について説明します。 Javaではメソッドの識別には、名前だけでなくて、仮引数の型なども見ています。 たとえば、今までお馴染のstartメソッドを自作のメソッドとして定義してみましょう。


	void   start(   int    x  )  {
		.........
	}
	

これは、今まで定義してきたstartメソッドとはちょっと違うようです。それは、引数の型が違うからです。 Javaでは、同じ名前でも違うパラメータの型を持っているメソッドは別物とみなします。 これを名前の多重定義(Overloading)と呼んでいます。 そのため、上のようなちょっと形の違うstartメソッドを定義しても構わないのです。 そこでメソッドを識別するために用いられるメソッドの特徴のことをシグネチャ(Signature)と呼んで、 名前だけの識別と区別しています。これは、次のような要素で構成されています。

▼シグネチャを決める要素
 ・メソッドの名前
 ・返す値の型
 ・仮引数の個数および順序とそれぞれの仮引数の型

同じ名前を持っていても、シグネチャが違っていれば、メソッドは別のものだと認識されます。 しかし、混乱を避けるために、なるべく既存の名前と衝突しないように気をつけた方が無難でしょう。 それはプログラムの読みやすさを維持するためです。 startやpaintといった名前を別の自作のメソッドにつけた場合には、後で混乱することになる可能性が高くなります。

☆別の自作のメソッドを定義してみる

それでは、メソッド定義の書式を使って自作のメソッドを作ってみましょう。 呼び出されたら、文字端末にメッセージを出して少しの間休憩するという、 ちょっとおさぼり気味のメソッドを作ってみましょう。 休憩するために、タートル・グラフィックスで用意されている、指定された秒数休憩するための sleep メソッドを用いましょう。 このメソッドの定義は、startメソッドなどと同様に、クラスの定義の中に記述します。

		
	void  shortSleep( ) {
		printLine( "Sleeping" );
		int  n = 1;
		while ( n <= 5 ) {
			sleep( 1 );		// 1秒休み
			printLine( n + " seconds" );
			n = n + 1;
		}
		printLine( "Awaked" );
	}
		

上記のメソッドは、文字端末にSleepingと表示してから、1秒休んだごとに秒数を表示し、 その後文字端末にAwakedと表示するだけの単純なメソッドです。 何も返さないので、voidと指定されています(外部に公開しませんのでpublicはつけていません)。 またパラメータを特に受け取りませんので、丸括弧の中には何も書かれていません。

☆定義された引数のないメソッドの呼出し

それでは、このshotSleepメソッドをstartメソッドから呼び出して使ってみましょう。 startメソッドの部分だけを記述します。

		
	public  void  start(  ) {
		Turtle  turtle = new Turtle( this );
		shortSleep( );			//  自作のメソッドを呼び出して、使ってみた
		turtle.rotate( 90 );
		shortSleep( );			// またもや、休むタートル
		turtle.forward( 50 );
	}
		

まずタートルが表示されてから、5秒休んで、やっと回転し始めたと思ったら、 さらに5秒休んで、やっと動き始めます。 同時に、文字端末の出力用のターミナルウィンドウが実行環境で用意されていれば、 その中にSleepingと休んだ秒数と、Awakedが休むたびに表示されていきます。 呼出しは、メソッドの名前をそのまま書くだけで構いません。 正式には、プログラムの中のメソッドを呼び出すということで、プログラムのオブジェクト自身を示す thisを用いて、 次のように記述することもできますが、大抵はこのような thisをつける必要はありません。

		
	this.shortSleep( );		//  オブジェクト自身のメソッドを呼び出すことを明示する場合
		

☆実習7-2 shortSleepメソッドの体験

上記のshortSleepメソッドを繰返しの中から呼びだして、六角形を描くプログラムを作りなさい。各辺を描く毎に、 回転をした毎に、休みます。随分な、さぼりやさんです。クラス名はPractie0702とします。

☆メソッドの実行について

先ほどの例について、メソッドの呼出しについて、どのような実行の順序になっているか図を使って追いかけていきましょう。 まず、タートル・グラフィックスのウィンドウが表示され、実行の主体がstartメソッドに移ります。 startメソッドでは、タートルを作成した後、注目する自作のメソッドのshortSleepが次に呼び出されます。 実行の主体は、shortSleepに移り、printLineやsleepなどのメソッドが呼び出され、実行が進んで行きます。 2番目のprintLineを呼び出して、もうすることがなくなったら、呼出し側のstartメソッドに戻ります。 そして、次のturtle.rotateが呼び出されていきます。 メソッドは呼び出された場所の次の場所に戻るということを改めて確かめてください。

図7-2 shortSleepメソッドの呼出しと戻り


7−2.インスタンス変数を使う

■メソッドの中で使用する変数

実習7-1で作ったメソッドは、それぞれのメソッドの中でタートルを作っていました。 ですから、メソッドが呼び出されるたびにタートルが作成されていきます。 また、メソッドの終了と共に、タートルへの参照がなくなってしまいます。 基本的に、変数は、宣言されたブロックの外側では生きることができないからです。 メソッド内のブロックで宣言された変数は、局所変数あるいはローカル変数(Local Variable) と呼ばれています。 そのため、クラスの定義の本体のブロックの方で、クラスの中で定義されたメソッドのすべてで 使用できる変数を宣言する必要があります。

 public class クラス名 {
  クラスブロック内での変数宣言
 }

turtleという変数をdrawSquareというメソッドの定義の中のブロックで使うためには、 次のように予め書いておかないといけません。旧来のプログラミング言語では、このような名前の使用の 仕方を「大域宣言(Global Declaration)」あるいは「グローバル宣言」と呼んだりします。

		
	public class クラス名 {
		Turtle  turtle;
	}
		

ところで、大域変数宣言された変数は初期化することができません。 これは、タートル・グラフィックスのライブラリの制約から来るもので、一般のJava言語の制約ではありません。 宣言だけが記述できて、代入はできませんので、注意してください。

■大域変数宣言(インスタンス変数宣言)の仕方

Java言語では、メソッドブロックの外(かつクラスブロックの中)で変数宣言を行ないます。 そのように宣言された変数は、オブジェクト指向言語ではインスタンス変数(Instance Variable)、 Java言語ではオブジェクト・フィールド(Object Field)と呼ばれていますが、 すべてのメソッドから参照・代入できるようになります。 なお、Turtleクラスのオブジェクトを作成して代入したり、整数などの変数に特定の初期値を代入するのは、startメソッドの中で行ないます。

public class Sample {
インスタンス変数の宣言はこのブロック内で行ないます。
public void start( ) {
startメソッド内で宣言された変数はローカル変数と呼ばれます。
}
}

たとえば、タートルがインスタンス変数として、クラス内のすべてのメソッドから参照できる形で プログラミングした例を以下に記述します。 タートルを示す変数turtleは、インスタンス変数として宣言されていますが、実際にタートルのオブジェクトを 作るのはstartメソッドの中にして下さい。そうしないと、うまく動かなくなります。 このプログラムでは、startメソッド以外に、drawTriangleとdrawSquareの2つのメソッドを自作で 定義しています。 この2つのメソッドは、startメソッドから呼び出されて実行されます。


	import    sfc.turtle.TurtleFrame;
	import    sfc.turtle.Turtle;
	
	public class InstanceVariableSample extends TurtleFrame {
	
		public static void main( String [ ] arg ) { new InstanceVariableSample( ); }
		
		//ここに大域変数の宣言を行ないます。すべてのメソッドで参照することができます。
		Turtle   turtle;
		
		public void start( ) {
			turtle  = new Turtle( this );	// オブジェクトを作成して代入するのは、startメソッドの中で
			drawTriangle( );
			turtle.rotate( 90 );
			drawSquare( );
		}
		
		void drawTriangle( ) {  // 三角形を描くメソッド
			turtle.rotate( 30 );
			int  i = 1;
			while ( i <= 3 ) {
				turtle.forward( 50 );
				turtle.rotate( 120 );
				i = i + 1;
			}
			turtle.rotate( -30 );
		}
			
		void drawSquare( ) { // 四角形を描くメソッド
			int  i = 1;
			while (  i <= 4 ) {
				turtle.forward( 50 );
				turtle.rotate( 90 );
				i = i + 1;
			}
		}

	}
		

熟練プログラマでもよくありがちなのは、メソッドを定義して、それで気持ちが落ち着いてしまって、 メソッドを呼び出す記述を入れるのを忘れることです。 メソッドを折角定義しても、呼び出されない限り実行されることはありません。

☆実習7-3 1つのタートルによる家を描くプログラムの記述と実行

上記の家を描くプログラムを入力し、横に7つ同じ大きさの家が並ぶように記述しなおしてみなさい。 クラス名はPractie0703とします。

☆実習7-4 格子をまた描く

drawSquareメソッドを用いて、縦5列、横5行の四角形から構成される格子枠を描きなさい。 各々の四角形の間は縦にも横にも、適当な間隔が空いているものとします。 クラス名はPractice0704とし、コンパイルして、実行してみなさい。

☆インスタンス変数の初期値

メソッド内で宣言されるローカル変数(あるいは局所変数:Local Variables)と違い、インスタンス変数は 初期化しなくても、初期値が代入されています。それぞれの型の変数では以下のようになっています。

型名初期値
int0
long0L
booleanfalse
double0.0
Stringnull

String(文字列)は、オブジェクトなので、オブジェクトとして何も指していないという値(null)が代入されて います。他のクラスのオブジェクトも、同じようにインスタンス変数として宣言された場合は、このnull値が代入 されています。 ただし、インスタンス変数はタートル・グラフィックスのライブラリを使っている限りは、上記の値が代入されていることを 前提とするまでで、宣言時に初期値を代入することができませんので、注意してください。

☆ローカル変数によるインスタンス変数の隠蔽

ローカル変数と同じ名前のインスタンス変数があった場合は、ローカル変数が優先されてしまいます。 これは、ローカル変数とインスタンス変数の型が異なっても同じです。 つまり、メソッドの中からはインスタンス変数が見えなくなってしまいます。 これを変数の隠蔽(Shadowing)と呼んでします。

たとえば、次の例では、インスタンス変数とローカル変数の型や値が異なりますが、ローカル変数の方が優先 されています。


	import  sfc.turtle.TurtleFrame;
	
	public class ShadowingSample extends TurtleFrame {
		int  x ;		// インスタンス変数として宣言したx
		
		public void start( ) {
			x = 89;			// インスタンス変数xに値を代入する
			printValueX( );		// 自作のメソッドを呼び出した
		}

		void printValueX( ) {
			int   x = 43;			// printValueXメソッドのローカル変数として宣言したx
			printLine( "The value of x is " + x );		// ローカル変数のxが参照される
		}

	}
		

タートル・グラフィックスの場合は、劇的にこの効果が現れてしまいます。間違えて、メソッド内部でも 同じ名前のタートル変数を宣言してしまった場合です。


	import  sfc.turtle.TurtleFrame;
	import  sfc.turtle.Turtle;
	
	public class ShadowingTurtleSample extends TurtleFrame {
	
		public static void main(String [ ]  args) {  new ShadowingTurtleSample( ); }
		
		Turtle turtle;		// インスタンス変数として宣言したturtle、初期化されていない
		
		public void start( ) {
			Turtle  turtle = new Turtle( this );		// ローカル変数として宣言されたturtle、オブジェクトが作られている
			moveMyTurtle( );
		}
		
		void moveMyTurtle( ) {
			turtle.rotate( 90 );			// 初期化されていないインスタンス変数のturtleを回転させている
			turtle.forward( 150 );		// 初期化されていないインスタンス変数のturlteを動かしている
		}
	}
		

これをやってしまうと、タートルは初期化されていないので回転も移動もしませんので、 画面上にはタートルや軌跡がまったく表示されません。 その理由は、インスタンス変数の方のturtleではなく、ローカル変数の方のturtleが初期化されたからです。

変数の隠蔽が行なわれてしまっているとき、メソッド内部から、インスタンス変数の方の変数を参照する記述の 仕方があります。オブジェクト自身を示す「this」を利用する方法です。


	this.x = 43;			// インスタンス変数の方のxに10.9を代入する
	this.turtle = new Turtle( this );		//  インスタンス変数の方のturtleにオブジェクトを作って参照させる
		

7−3.引数のあるメソッドの定義と呼出し

■パラメータあるいは引数について

四角形を描くときなど、一辺の長さをメソッドに直接渡せると便利です。 メソッドを呼び出すときに、何らかの情報あるいはデータを一緒に渡せるという機能が、 ほぼすべてのプログラミング言語に備わっています。 このメソッド呼出しのときに一緒に渡されるデータのことを「パラメータ(Parameter)」 あるいは「引数(Arguments)」と呼ばれます。 このとき、パラメータあるいは引数には2つの側面があります。 呼出し側のプログラムでは、パラメータに実際の値(定数値)を入れてメソッドに渡します。 このときのパラメータ、つまり呼び出されたときの値のことを「実パラメータ」あるいは「実引数」と 呼びます。 一方、呼び出されたメソッドの方では、それを何らかの形で受け取らなければなりません。 ほとんどのプログラミング言語では、変数という形で受け取ります。 この実パラメータを受け取るための変数のことを、「仮引数」と呼んでいます。

☆引数のあるメソッドの定義

メソッドに対して、描画するための座標やサイズ、回転角度などの情報を渡して、呼び出したい場合は多いでしょう。 このときは、メソッドの名前にある丸括弧の中に引数(あるいはパラメータとも呼ばれています)を記述します。 メソッドの定義の書式と呼出しは、それぞれ以下のようになります。[A]…は、Aの0回以上の繰返しと思って下さい。

▼引数のあるメソッド定義の書式
 void メソッド名( 型名 仮引数の変数名 [, 型名 仮引数の変数名 ]… ) ブロック

仮引数の名前は、メソッドの中でだけ使える変数名と思って下さい。 それから、型名は「整数型」と「文字列型」が使えますが、ほぼ使うのは整数型だと思って下さい。 たとえば、次のメソッド「drawSquare」では、一辺の長さを受け取る仮引数として、「size」という変数を用いています。

		
	void drawSquare( int size ) {
		int n = 1;
		while ( n <= 4 ) {
			turtle.forward( size );			// turtleはインスタンス変数として宣言されていて、初期化されているものとします
			turtle.rotate( 90 );
			n = n + 1;
		}
	}
		

もちろん、複数の仮引数を指定することも可能です。次のメソッドは、正多角形を描くものですが、 角数「corners」と一辺の長さ「size」の2つの引数を受け取っています。

		
	void drawRegularPolygon( int corners, int size )  {
		int n = 0;
		while ( n < corners ) {
			turtle.forward( size );			// turtleはインスタンス変数として宣言されていて、初期化されているものとします
			turtle.rotate( 360 / corners );
			n = n + 1;
		}
	}
		

☆引数のあるメソッドの呼出し方

今度は定義した引数のあるメソッドを呼び出して使ってみましょう。 呼び出して使わない限り、定義したメソッドが使われることはありません。 呼出しの書式は次のようになります。

 ▼引数のあるメソッド呼出しの書式
  メソッド名( 実引数の式 {, 実引数の式 }… );

整数の引数が1つ必要な「drawSquare」の場合、どのように呼び出すのか記述してみましょう。

		
	drawSquare( 40 );			// 一辺の長さが40の正方形を描く
		

もし、仮引数が複数あれば、順番を入れ替えずに実引数の個数分だけ式を記述して、メソッドを 呼び出します。先ほどの「drawRegularPolygon」を呼び出して使ってみましょう。

		
	drawRegularPolygon( 6, 30 );		// 一辺の長さが30で、正六角形を描く。
		

プログラム全体では、この「drawRegularPolygon」メソッドを利用した記述は、次のような形になります。 これは、実はメソッド呼出しを利用した形での、課題5-9の答えでもあります。

		
	import  sfc.turtle.TurtleFrame;
	import  sfc.turtle.Turtle;
	
	public class DrawRegularPolygonsTester extends TurtleFrame {
	
		public static void main(String [ ]  args) {  new DrawRegularPolygonsTester( ); }
		
		Turtle turtle;		// インスタンス変数として宣言したturtle
		
		public void start( ) {
			turtle = new Turtle( this );		// turtleを初期化
			int n = 3;		// 正三角形から
			while ( n <= 12 ) {		// 正十二角形まで
				drawRegularPolygon( n, 40 );		// 一辺の長さを40として正多角形を描く
				n = n + 1;
			}
		}

		void drawRegularPolygon( int corners, int size )  {
			int n = 0;			// こちらの変数nは、startメソッドのnとは別の変数です
			while ( n < corners ) {
				turtle.forward( size );			// turtleはインスタンス変数として宣言されていて、初期化されているものとします
				turtle.rotate( 360 / corners );
				n = n + 1;
			}
		}
	}
		

☆課題7-1 正多角形を回転させながら描画するプログラム

まず、startメソッドの中で正何角形を描くのかダイアログで入力してもらいなさい。そして、上記の drawRegularPolygonメソッドを用いて、下記のように正多角形が回転しながら大きくなるようなプログラムを 記述しなさい。クラス名は、DrawPolygonSpiralにて作成し、コンパイルして、実行しなさい。 ちなみに、下記の図では正六角形を描いています。

他の例として、簡単すぎて、あまり意味はないのですが、引数として与えられた文字列にちょっとした前置きをつけて、 文字端末に表示するメソッドを定義してみましょう。


	void  drawMessage(  String  message ) {
		printLine( "受け取った文字列: " + message );
	}
		

このメソッドでは、文字列を引数として受け取り、それに仮にmessageという名前をつけています。 これは、変数として使うことができます。 このメソッドでは、この変数を更に別のメソッドprintLineを呼び出すときのパラメータとして用いています。

startメソッドから、このメソッドを利用してみましょう。 アプリケーションから文字端末に出すという使い方は、実行テストの情報を出す以外にあまり行なわれませんし、 アプリケーションが.jarファイル形式になっているときは、ダブルクリックして起動しても、どこにも表示されない (正確にはシステムのコンソールに表示されている)のですが、ここではBlueJやXcodeなどで、開発環境上に 仮想的な文字端末があって表示できると仮定して、使ってみます。 実際のプログラム全体を記述してみましょう。 drawMessageメソッドも、startメソッドと同様に、クラスの定義の中に記述されているのがわかります。

		
	import 	sfc.turtle.TurtleFrame;

	public  class MethodTester  extends TurtleFrame {

		public static void main( String [ ] arg ) { new MethodTester( ); } 
	
		public  void  start( )  {
			drawMessage( "I am called by Application." );
			drawMessage( "Do you understand on calling method?" );
		}

		void  drawMessage(  String  message ) {
			printLine( "受け取った文字列: " + message );
		}
	}
		

文字端末へ出力できるような実行環境で実行すると、描画要求がされる度に、 startメソッドからdrawMessageへ呼出しが行なわれ、次のように2行のメッセージが文字端末に表示されることになります。


	受け取った文字列: I am called by Application.
	受け取った文字列: Do you understand on calling method?
		

☆引数の評価とメソッド呼出し

復習になりますが、ここでもう一度、引数について整理してみましょう。 引数を必要とするメソッドは、呼出し側で指定した引数を評価し、必ず最終的に定数に置き換えてから、 呼び出されるメソッドに渡されます。 すなわち、引数の評価を先に行なってから、メソッド呼出しが行なわれるのです。 それぞれの側での引数を区別するために、次のように呼び分けています。

実引数(じつひきすう:Actual Argument) 呼出し側で指定した式や定数
仮引数(かりひきすう:Formal Argument) メソッド側で受け取るために指定した変数

引数は、パラメータは(Parameter)とも呼ばれることがありますので、仮引数のことは、 仮パラメータ(Formal Parameter)と呼ぶことがあります。 同じように実引数も、実パラメータ(Actual Parameter)と呼ばれることがあります。


図7-6 drawMessageメソッドの呼出しにおける実パラメータと仮パラメータ

実引数が先に評価されてメソッドが呼び出される例を見てみましょう。 実引数として(計算)式を利用してみます。 先ほどのdrawMessageを呼び出す際に、次のように記述されたとします。

		
	int 	bcount = 12;
	drawMessage(  "日本の戦艦は" + bcount + "隻である" );
		

この場合には、括弧内の実引数の式がまず評価されます。 文字列の結合が行なわれ、評価結果として、"日本の戦艦は12隻である"という文字列定数が生成されます。 呼び出されたdrawMessageメソッドの実行では、この文字列定数が仮引数の変数に代入されますので、 最初に次のような代入が行なわれたの同じに状態で実行が開始されます。

		
	message = "日本の戦艦は12隻である";
		

ちなみに、ここでカウントされている12隻の戦艦とは第2次世界大戦の頃の、 大和・武蔵・長門・陸奥・伊勢・日向・金剛・榛名・山城・扶桑・比叡・霧島を指します。

☆課題7-2 四角形を内側に描く

大きい四角形から、小さい四角形まで中心を揃えて描くプログラムを作りなさい。 引数のあるメソッドを定義します。 メソッドとしては、一辺のサイズ受け取って四角形を描くdrawSquareを定義します。 各々の四角形の間には適当な間隔が空いているものとします。 クラス名はPyramidLocusとし、コンパイルして、実行してみなさい。

☆引数を必要とするメソッドの使用例

drawMessageメソッドは、動作原理を説明するために用いましたが、もう少し実用的なメソッドを記述してみましょう。 たとえば、正三角形を書くようなメソッドを定義しておいて、これをstartメソッドから呼び出せるようにしてみましょう。 このメソッドに与えるべき情報は、次のようなものが考えられます。

・上部の頂点の相対的なx座標、y座標
・1辺の長さ

これだけの情報を引数として渡せば、正三角形が描けます。次のように定義します。 タートルのペンを上げてから、相対的な位置をforwardとrorateメソッドで指定し、角度を30度に設定してから、 rotateで回転させて、forwardメソッドを使って3本の線を引いています。


	void  drawTriangle(  int   x,  int   y,   int   len ) {
		turtle.penup( );
		turtle.rotate( 180 );
		turtle.forward( y );		// 相対的にy方向に移動
		turtle.rotate( -90 );
		turtle.forward( x );		// 相対的にx方向に移動
		turtle.rotate( -90 );
		turtle.pendown( );
		turtle.rotate( 30 );		// 描き始めに30度
		int  i=1;
		while (  i <= 3 ) {
			turtle.forward( len );
			turtle.rotate( 120 );
			i = i + 1;
		}
		turtle.rotate( -30 );
	}
		

このメソッドをstartメソッドから呼び出して使ってみましょう。 まずは、定数値で1つだけ三角形描くように使ってみます。 変数turtleは、インスタンス変数として宣言されているものとします (クラスのブロックに「Turtle turtle;」という変数宣言がされているものとします)。


	public  void start(  )  {
		turtle = new Turtle( this );
		drawTriangle( 100, 100,  50 );		//  定数値で
	}
		

さらに、繰返しで実引数の値を変えながら、呼出しを行なってみましょう。どのような図が描かれるでしょうか?


	public  void start(  )  {
		turtle = new Turtle( this );
		int  i=10 ;
		while (  i<100 ) {
			drawTriangle(  5,  -3,  100 - i*2 );		//   頂点の座標値を変えながら
			i = i + 5;
		}
	}
		

☆実習7-5 頂点を指定する正三角形メソッドを使う

上記のメソッドをプログラムとして完成させて動かしてみなさい。 クラス名はPractice0705として作成し、実際にコンパイルして、実行して見なさい。

図7-8 drawTriangleメソッドを呼び出して描かせた正三角形の図

☆仮引数とインスタンス変数との衝突

ローカル変数のときと同様に、インスタンス変数と仮引数が同じ名前だったらどうなるでしょうか? 次のプログラムは、2つのメソッドを持っています。 startメソッドからdummyメソッドを呼び出しているのですが、dummyメソッドでは変数のxに10を代入しています。 変数xは、インスタンス変数(整数で最初に何も代入されていない場合は0が代入されています)と dummyメソッドの仮引数と両方に宣言されています。 さて、printLineでは一体どのような値が文字端末に表示されるのでしょうか?


	import 	sfc.turtle.TurtleFrame;

	public class ShadowingTester extends TurtleFrame {
	
		public static void main( String [ ] arg ) { new ShadowingTester( ); }
	
		int 	x ;			// インスタンス変数として宣言されたx

		public  void start(  )  {
			x = 30;		// インスタンス変数に30を代入した。
			dummy( 20 );
			printLine( x );	// このxは、インスタンス変数の方を指す
		}

		void dummy( int  x ) {  // メソッドの仮引数として宣言されたx
			x = 10;			// このxは、仮引数変数の方を指す
		}
	}
		

結局、ローカル変数名で起こったことと同じことが起こります。 インスタンス変数と同じ名前で仮引数の変数やローカル変数が宣言されている場合は、 仮引数あるいはローカル変数の方が優先されます。 この例では、dummyメソッドの中での代入は、仮引数の変数に行なわれています(注1)。 インスタンス変数には何も代入されませんので、文字端末には、元々の値の0が表示されることになります。 結局、仮引数変数のxがインスタンス変数の方のxを見せなくしていました。 これも変数の隠蔽(Shadowing)と読んでいます。

仮引数やローカル変数で同じ名前が使われていたときに、インスタンス変数の方を参照する方法があります。 オブジェクトそのものを指し示したい場合には、「this」という特別な名前の変数が用意されています。 これを用いて、dummyメソッドの中で次のように記述すれば、たとえ同じ名前の変数が宣言されていたとしても、 インスタンス変数の値を参照したり、変えることができます。


			this.x = 10;			// インスタンス変数の方のxに10を代入する
		

☆メソッド名と変数名の衝突

1つのクラスの定義の中で、メソッド名と変数名(インスタンス変数やローカル変数)が同じ名前を持っていたとしてもコンパイラはエラーを出しません。メソッドはシグネチャで識別されているからです。 たとえば、タートル・グラフィックスではstartメソッドがありますが、 startという名前のローカル変数やインスタンス変数を宣言しても構いません。 しかし、このようなメソッドと同じ名前を持つ変数がプログラム中に存在すると、かなり紛らわしいので、 なるべく避けた方が無難でしょう。

※注1 仮引数の変数は、普通の変数として扱えますので、仮引数にも値を代入できてしまいます。 この場合は、元々保持していた値、すなわち、呼出し側から渡された実引数は書き換えられてしまいます。

☆実習7-6 円弧を引数のあるメソッドで行なって記述してみましょう

メソッド名をdrawArcとしましょう。1度曲がるあたりの「歩数(step)」と「角度差(diff)」の2つを仮引数とします。 歩数は1度進むときのドット数になります。 あとは、実習5-4のプログラムを利用してみました。

		
	// 円弧を描くメソッドを記述します、1度回るときに、stepドットだけ進みます。
	void drawArc( int step,  int diff ) {
		if ( diff >= 0 ) {   // 角度差がプラスのばあい
			int  angle = 0;
			while ( angle < diff ) {
				turtle.forward( step );
				turtle.rotate( 1 );
				angle = angle + 1;
			}
		} else {   // 角度差がマイナスのばあい
			turtle.rotate( 180 );
			int  angle = 0;
			while ( angle < -diff ) {
				turtle.rotate( -1 );
				turtle.forward( step );
				angle = angle + 1;
			}            
		}
	}
		

この円弧を描くメソッドを、startメソッドから呼び出してみなさい。 クラス名はPractice0706として作成し、実際にコンパイルして、実行して見なさい。

☆多重に呼び出されるメソッド

自作のメソッドを複数定義した場合は、1つのメソッドから別の呼び出すことができます。たとえば、先ほどの三角形を描くメソッドを利用して、更に家を描くようなメソッドを定義してみましょう。二等辺三角形が家の屋根で、その下に家の壁作ってみましょう。渡す引数としては、家の屋根の頂点のx座標とy座標、家のサイズをの4つです。これらの情報をそのまま用いて、drawTriangleを呼び出してやります。そうすると、屋根の三角形が描かれますので、その下にdrawRectを用いて、壁を描画します。家のサイズは、壁の1辺のサイズになっています。

	void drawRect( int  size ) {
		int  i=1;
		while (  i <= 4 ) {
			turtle.forward(  size );
			turtle.rotate( 90 );
			i = i + 1;
		}
	}

	void  drawHouse( int   x,  int   y,  int   size ) {
		drawTriangle( x, y, size );
		turtle.penup( );
		turtle.rotate( 150 );
		turtle.forward( size );
		turtle.rotate( 30 );
		turtle.pendown( );
		drawRect( size );
	}
		
このメソッドをstartメソッドから利用して、使ってみます。

	public  void start( ) {
		turtle = new Turtle( this );
		int   i=20 ;
		while (  i <= 170 ) {
			drawHouse(  i,  100,   20 );		//   頂点のx座標値を変えながら
			 i = i + 30;
		}
	}
		

多重に呼び出されるメソッド

☆課題7-3 家を描画するプログラム

上記の家を描画するプログラムで、同じ大きさの家が、縦に7つ、横に7つの家が並ぶように描いて見なさい。 どこに描画されるかで、色を変えても構いません。 DrawHouseAreaにて。

☆実習7-7 再び8つの家

小さい家から大きい家まで、横に8つの家を造るようなプログラムを作りなさい。 課題5-5と同様に、底辺を揃えるようにします。 引数のあるメソッドを定義します。メソッドとしては、家の一辺のサイズを受け取るdrawHouseと、 一辺のサイズ受け取って四角形を描くdrawSquareと、同じく一辺のサイズを受け取って正三角形を描くdrawTriangleを 定義します。 各々の家の間には適当な間隔が空いているものとします。 クラス名はPractice0707とし、コンパイルして、実行してみなさい。

☆同名で多重定義されたメソッド

Javaでは、メソッドの判別を、シグネチャで判別していますので、メソッドの名前だけでなく、 パラメータの型や数についても解析しています。 ですから異なる型のパラメータを受け取る「同じ名前のメソッドを多重に定義」できます。 これをJavaではメソッド名のオーバーロード(Overload)と呼んでいます。 また、プログラミング言語一般的には、そのようなメソッドを総称して、 多相型(Polymorphic)のメソッドとも呼ばれています。

たとえば、タートルグラフィックスのライブラリでは、displayDialog, displayInputDialog, print, printLineなどが多重定義されています。 同じ名前ですが、実引数に型よって動作が違います。

	
同じ名前のメソッド使用例
displayDialog( String s )displayDialog( "Hello, Java" );
displayDialog( int n )displayDialog( 356*893 );
displayDialog( long nn )displayDialog( 6734673L );
displayDialog( double d )displayDialog( 23.3 );
  
displayInputDialog( String m, String s )String message = displayInputDialog( "Input sentence", "I love programming." );
displayInputDialog( String m, int n )int x = displayInputDialog( "Input Integer", 3 );
displayInputDialog( String m, long nn )long xx = displayInputDialog( "Input Long Integer", 3L );
displayInputDialog( String m, double d )double d = displayInputDialog( "Input Real Number", 3.3 );
  
print( String s )print( "Hello, Java" );
print( int n )print( 356*893 );
print( long nn )print( 6734673L );
print( double d )print( 23.3 );
  
printLine( )printLine( ); // 改行だけします
printLine( String s )printLine( "Hello, Java" );
printLine( int n )printLine( 356*893 );
printLine( long nn )printLine( 6734673L );
printLine( double d )printLine( 23.3 );

☆実習7-8 拡張された円弧メソッドの記述

実習5-4で作成した、毎回回転する度に進むドット数と中心角(角度差)を指定して円弧を描くメソッドを作って、 プログラムから利用して見なさい。また、タートルが1回進むにつき、何度回転させるのかも指定できるメソッドも作って、 あわせてテストしてみてください。クラス名はPractice0708にて。 メソッドの概形は次のような形になります。

		
	void drawArc( int step,  int diff, int reduce ) {
			// ここに円弧を描く処理を記述します。
	}
		

このdrawArcは、同名で(多重定義)で定義されたメソッドになっています。どうして、reduceという3つ目の 引数が必要だったのかは、上の図から見てわかりますように、この3つ目の引数が、縮尺率になっています。 reduce=2の場合は、2度ずつ回転しますので、円弧は1/2の大きさになります。これは、step=1として呼び出した場合でも、 円弧が大きすぎる場合に用いてください。同じように、reduce=3のときは1/3に、reduce=4のときは、1/4になっています。 startメソッドに次のような断片を書いてみて、タートルの円弧が小さくなるのを確かめてみてください。

	
	public  void start( ) {
		int   i=1 ;
		while (  i <= 5 ) {
			turtle = new Turtle( this );
			drawArc( 1, 120, i ); // 120度の角度差で、同じ円弧を描いてみる
			 i = i + 1;
		}
	}
		

☆実習7-9 葉っぱを描く

上記の実習7-6の「円弧を描く」メソッドを利用して、葉っぱを描くメソッドを以下のように定義して、葉っぱを 描いてみなさい。クラス名はPractice0709とします。startメソッドと、3つの多重定義されたdrawLeafメソッドを記述しています。


	public  void start( ) {
		turtle = new Turtle( this );
		turtle.rotate( 30 );
		drawLeaf( );
		turtle.rotate( 60 );
		drawLeaf( 2 );   // 大きさを指定して

		// タートルを別の場所に持っていきます
		turtle.penup( );
		turtle.rotate( -60 );
		turtle.forward( 180 );
		turtle.pendown( );

		int i=2;
		while ( i <= 8 ) { 
			drawLeaf( 2, i );  // だんだん膨らみを増していく
			turtle.rotate( 124-i*15 ); // この124は感覚です。気持ちってことで。
			i++;
		}
	}
	
	// 単なる葉っぱを描くメソッド
	void drawLeaf( ) {
		drawArc( 1, 120 );
		turtle.rotate( 60 );
		drawArc( 1, 120 );
	}
	
	// 1度曲がるあたりの大きさを指定して葉っぱを描くメソッド
	void drawLeaf( int step ) {
		drawArc( step, 120 );
		turtle.rotate( 60 );
		drawArc( step, 120 );
	}
	
	// 大きさと膨らみ(1〜11)を指定して葉っぱを描くメソッド
	void drawLeaf( int step, int swell ) {
		int angle = 15 * swell;
		drawArc( step, angle );
		turtle.rotate( 180-angle );
		drawArc( step, angle ); 
	}
		

☆課題7-4 猫の目(円弧2つ)

上記の実習7-6もしくは実習7-8の「円弧を描く」メソッドを利用して、次のような猫の目を描きなさい。 CatsEyeというクラス名で作成し、コンパイルして、実行してみなさい。

☆課題7-5 蝶

上記の実習7-6もしくは実習7-8の「円弧を描く」メソッドと実習7-9の「葉っぱを描く」メソッドを利用して、次のような蝶を描きなさい。 ヒントは、曲線を1度ずつ曲げながら描く場合、蝶の見掛けはかなり大きいです。 下記の図は縮小してあります。角度としては、120度や60度などを多用しています。 DrawButterflyというクラス名で作成し、コンパイルして、実行してみなさい。

☆課題7-6 花を描いてください

上記の実習7-6もしくは実習7-8の「円弧を描く」メソッドと実習7-9の「葉っぱを描く」メソッドを利用して、次のような花を描きなさい。 かなり時間が掛かります。1度ずつ描くのでどのようにしてもタートルがのろいからです。 「NoWait」とデバッガのSTOP(コンパイル後に行番号をクリックで使えます)を利用してください。 120度や60度などを多用しますが、茎だけは45度を使っています。 あるいは、花びらや葉を45度と90度あたりを利用しても描けるでしょう。 DrawFlowerというクラス名で作成し、コンパイルして、実行してみなさい。

■タートルを引数として受け渡す

インスタンス変数でタートルを定義してきましたが、タートルを引数として受け渡すと、インスタンス変数に する必要がありません。また、複数のタートルを利用する場合に、指定したタートルに対して操作を仕向けるように プログラミングできるので便利です。メソッドの定義においては、次のようにタートルを貰う仮引数の変数を 宣言しておきます。

void 呼ばれる側のメソッド( Turtle t, 他の仮引数の宣言 )

例えば、次のような感じです。

		
	void drawSquare( Turtle t, int size ) {
		for ( int i=1; i <= 4; i=i+1 ) {
			t.forward( size );
			t.rotate( 90 );
		}
	}
	

呼び出し側は、ローカルに宣言し、初期化したタートルを実引数として受け渡してあげます。

		
	public void start(  ) {
		Turtle turtle = new Turtle( this );
		for ( int i=1; i <= 72; i=i+1 ) {
			drawSquare( turtle, i * 2 );
			turtle.rotate( 5 );
		}
	}
	

たとえば、先ほどの実習7-9のプログラムをこの方法で書き直してみます。


	public  void start( ) {
		Turtle turtle = new Turtle( this );
		turtle.rotate( 30 );
		drawLeaf( turtle );
		turtle.rotate( 60 );
		drawLeaf( turtle, 2 );   // 大きさを指定して

		// タートルを別の場所に持っていきます
		turtle.penup( );
		turtle.rotate( -60 );
		turtle.forward( 180 );
		turtle.pendown( );

		int i=2;
		while ( i <= 8 ) { 
			drawLeaf( turtle, 2, i );  // だんだん膨らみを増していく
			turtle.rotate( 124-i*15 ); 
			i++;
		}
	}
	
	// 単なる葉っぱを描くメソッド
	void drawLeaf( Turtle t ) {
		drawArc( t, 1, 120 );
		t.rotate( 60 );
		drawArc( t, 1, 120 );
	}
	
	// 1度曲がるあたりの大きさを指定して葉っぱを描くメソッド
	void drawLeaf( Turtle t, int step ) {
		drawArc( t, step, 120 );
		t.rotate( 60 );
		drawArc( t, step, 120 );
	}
	
	// 大きさと膨らみ(1〜11)を指定して葉っぱを描くメソッド
	void drawLeaf( Turtle t, int step, int swell ) {
		int angle = 15 * swell;
		drawArc( t, step, angle );
		t.rotate( t, 180-angle );
		drawArc( t, step, angle ); 
	}
	
	// 円弧を描くメソッド
	void drawArc( Turtle t, int step,  int diff ) {
		if ( diff > 0 ) {
			for ( int i=0 ; i < diff; i=i+1 ) {
				t.forward( step );
				t.rotate( 1 );
			}
		} else {
			for ( int i=0 ; i < -diff; i=i+1 ) {
				t.rotate( -1 );
				t.forward( step );
			}
		}
	}

	

7−4.中間課題制作

■ミルフルール(千花模様)とは

ミルフルール(Mille-fleur)は、 日本では千花模様と呼ばれています。 一面に花の模様を散りばめたような模様のことをさしています。 下記の図は、あくまでもタートルグラフィックスで描いた例です。この例の通りでなくて構いません。 花鳥風月ですから、蝶や月なども含めても構わないでしょう。ただ、女性の着物なので、あまり突飛な デザイン(形・色)は好まれません。

■ミルフルールを描きましょう。

ミルフルールの意味がわかったところで、自分のミルフルールをプログラムで作ってみましょう。 Millefleur名でというクラス名で作成し、コンパイルし、実行しなさい。 制約条件は以下の通りとします。

まず描くすべての種類のパターンを描き切るプログラムを作ります。それをメソッドの形で 記述し直します。このメソッドを繰返しの中で呼び出して、タートルの角度などを調整しながら、縦に移動させたり、 横に移動させたりさせます。少ない繰返し回数から始めます。 タートルは複数使っても構いません。 曲線を1度ずつ描くときのタートルはのろいです。制作では、このタートルの「のろい」に悩まされますので注意して下さい。 ほとんどうまくいって、待ちたくないときは、メニューの「NoWait」を利用してください。 あるいは、少しでも速くタートルを動かすためには、曲線を1度ずつ曲げるのではなくて、数度ずつ曲げながら 描くようにすると良いかも知れません。 また、特定のところで止めたい(タートルの角度や位置を知りたい)ときは、コンパイルしてから、 行番号をクリックすれば、そこでデバッガが起動されますので、 それを利用するといった方法もあります。

★描画するときに使う技法についてのいくつかの説明

(A) 画面のサイズを変更するのは、setSize( 横の長さ, 縦の長さ );で変更できます。大きく描きたいときに、利用して下さい。 また、タートルの描画に時間を取られたくないときは、setWaitTurtle( false );を 実行して下さい。setWaitTurtleで、falseからtrueまでの指定をした間は、アニメーションの部分が省略されて描画されます。

(B) 閉じた模様については、たとえば次の例を見てください。全体として一周まわって閉じています。 このような模様を入れてみて下さい。

	
	public void start( ) {
		Turtle turtle = new Turtle( this );
		for ( int i=1; i <= 36; i= i + 1 ) {
			for ( int j=1; j <= 4; j = j + 1 ) {
				turtle.forward( j * 5 );
				turtle.rotate( 90 );
			}
			turtle.rotate( 10 );
		}
	}
	

(C) タートルを上に向けるには、各タートルに対してsetAngle( 0 )を実行させます。 このようにしてから、タートルを次の描画位置に移動させると分かりやすいでしょう。 また、複数のタートルを使っている場合、描き終わったタートルを表示したくないない場合は、 描画後に、そのタートルに対してsetTurtleVisible( false )を 実行させます。役目を終えたそのタートルはひっそりと姿を消します。

(D) もし、塗り潰しを利用する際には、各タートルに対して、setFillLocus( true )を実行して下さい。 また、この状態になったタートルは、「ペンの色を変えるか、pendown」する度に新しい領域として、領域を塗ります (SFC Turtle Graphics 0.9.7.1版/BlueJ SFC 3.0.3版より)。 また、同じタートルで塗り潰しをするときに、後で描画した領域の方が、前面に表示されます。 重ね塗りをするときに、注意してください。

☆提出の仕方

SFSの方から提出してもらいます。提出物件は以下の2つになります。

プログラムは、ソースファイルの方を提出します。たとえば、「Project2011」というフォルダが、プロジェクトの位置で あれば、その中にある「Millefleur.java」というファイルを提出します。

Macintosh上では画面ダンプをショートカット(Shift+⌘+4)で部分的に撮ることができます。 このショートカットを押すと、カーソルが十字キーになりますので、ドラッグして範囲を指定してください。 そうすると「ピクチャn.png」というファイルがデスクトップ上に現れますのでそれをプレビューで見て下さい。 なお、nは、番号です。スナップショットを撮るたびに「ピクチャ1.png」、「ピクチャ2.png」、、、とファイルが できます。このファイルをSFSの方に提出してもらいます。 ただし、SFSの方は、日本語の名前では受け取れないみたいなので、「picture.png」 というように英語の名前に直してから提出してください。

Windowsでは、画面のスナップショットを撮るファンクションキーが用意されています。 それで画面全体のスナップショットを撮ります。しかしこの画面全体は、一時的にコンピュータに記憶されているだけ なので、これを「ペイント」などのアプリケーションを開いて、「貼り付け」を行なって、実際に画像として作ります。 貼り付けを行なった後は、ファイルとして保存します。このときに、Windows標準では、bmp形式というファイルになって いますが、ファイル保存のダイアログでポップアップメニューを開いて、「png形式」のファイルを選んでください。 決して、bmp形式の画像では提出しないでください。 拡張子が見える設定になっていれば、ファイル名に「.png」という拡張子が付いて見える筈です。 また、ファイル名に日本語を使わないでください。SFSの方が受け取れないからです。

☆提出の期限

2011年7月10日午後23:59分までとします。少し(2〜3時間)ぐらい遅れても大丈夫ですが、SFS上では 遅延提出として登録されます。成績には、その程度の遅れは考慮しませんので、とにかく2つの提出物を提出してください。 なお、この提出物がない場合、シラバスの規約に基づき、以降履修放棄と見なしますので、履修を継続されたい方は、 必ず提出をお願い致します。

☆実習7-10 大きさを指定して蝶を描く

実習7-8の「円弧を描く」メソッドを利用して、蝶を描くメソッドを大きさを変更できるように定義して、蝶を横に並べて 描いてみなさい。これを複数の段で描いていきますが、下の段に行くほど、蝶の大きさが大きくなるようにします。 クラス名はPractice0710とします。


7−5.戻り値を返すメソッド

■戻り値を返すメソッドの定義の仕方

引数は、呼出し側からメソッドへの引数の受渡しでした。 メソッドの実行の終了時には、メソッド側から呼出し側へ、何らかの情報を返すことができます。

☆メソッドの終了時における戻り値を返す文

メソッドの終了は、メソッドを定義しているブロックの最後まで実行が終わってしまえば、自動的に処理が呼出し側に戻ります。 しかし、明示的にメソッドを終了したい場合は、return文を用います。 メソッドをどの時点でも強制終了させて、呼出し側に制御を戻せます。 加えて、値を返すメソッドを作成したい場合は、呼出し側に値を返すときに、このreturn文の後に式を記述します。 次の書式のように、returnの後に式を記述することができます。

▼return文で値を返すときの書式
 return 式 ;

終了する前に、このreturn文の後に続く式が評価されます。 評価が終わって一定の値になりましたら、呼出し側にその値が戻されます。 ここで返される値のことを戻り値(Return Value)あるいは返り値と呼んでいます。 それでは、戻り値を返すようなメソッドを定義してみましょう。 まず、戻り値を返すので、その型を記述します。今までは、戻り値がありませんでしたので、voidと記述してきました。 たとえば、整数を返すのであれば、int と記述します。簡単なメソッドを定義してみましょう。


	int    square(   int    x  )  {
		int   y = x * x;
		return    y;
	}
		

このsquareというメソッドは引数として受け取った値の2乗を計算して、計算結果を戻り値として返します。 整数用になっていますので、仮引数も戻り値も型としてはint と記述されています。 わかりやすくするために、ローカル変数yを用意して、それに計算結果を保持させるように記述しています。 returnの後は、式を書くことができますので、このメソッドは直接的に次のように定義することもできます。


	int    square(   int    x  )  {
		return    x * x;
	}
		

もう1つの例を見てみましょう。 2つの引数を受け取り、大きい方の数を返すメソッドを定義してみます。


	int    greater(  int    x,   int   y ) {
		if ( x > y )  {  return  x ; }  else { return y; }
	}
		

図7-17 squareとgreaterの機能図

戻り値は1つしか記述できないのでしょうか?C言語系統のプログラミング言語は、メソッドは元々は関数的な取り扱いでしたので、 戻り値は1つだけしか記述できません。もし、複数の値を戻したいのであれば、配列にしたり、オブジェクトの形に直す必要があります。 いくつかのスクリプト言語(Luaなど)は、複数の値を戻す書式が用意されています。

もし、戻り値を返す指定をした(たとえば、型をvoidではなくint と記述した)のに、 return文がメソッドの定義の中に含まれていなかった場合はどうなるでしょうか? 大丈夫です。Java言語のコンパイラがちゃんと見張っていて、「return文がないよ」というエラーメッセージを出してくれます。

▼戻り値のあるメソッドの定義
 戻り値の型 メソッド名( 仮引数 ){
     :
  return 戻す値を示す式;
 }

☆メソッドの呼出しと戻り値の評価

戻り値のあるメソッドを呼び出して使ってみましょう。 次のように、代入文の式の中にメソッド呼出しを記述します。実引数の個数は、定義された仮引数の個数と同じ分だけ用意します。

▼戻り値のあるメソッドの呼出し
 変数 = メソッド呼出し( 実引数 );
もちろん、代入文など用意しないで、戻り値を受け取らなくても構いません。あるいは、メソッドの呼出しを複雑な式の中に埋没させても構いません。さて、それでは、先ほど定義したメソッドを、この書式に基づいて、呼び出して使うための記述をしてみましょう。変数を宣言しながら、代入を行なっています。

	int 	z = square( 30 );		// zには900が代入される
	int 	y = square( z + 20 );	// yには846400が代入される
	int 	w = greater( z,  y );	// wにも846400が代入される
		
他の自作のメソッドと同様に、アプリケーション(クラス)自身のメソッドであることを示すためには、次のようにthisをつけて呼び出すこともできます。

	int   positive =  this.square(  -30 );
		
このような呼出しを行なった場合、実際には、どのような実行の順番になっているのでしょうか?簡単な例を用いて、少し、実行の様子を追ってみましょう。 メソッドの呼出しの記述:

	int 	result = square(  45 * 10 );
		→45*10という式が評価されて、定数値450になり実引数としてsquareメソッドに渡されます。
		
メソッドの定義:

	int    square(   int    x  )  {   return    x * x ;   } 
		→squareが呼び出され、仮引数変数xに、実引数の定数値450が代入されます。
		→x * xと書かれた式の計算結果の定数値202500が戻り値として呼出し側に返送されます
		
メソッドの呼出し後の代入:

	int   result = square(  45 * 10 );
		→square( 45 * 10 )の部分が、戻り値の定数値202500に置き替わります。
		→変数resultを宣言し、result = 202500;という代入文が実行されます。
		
このように、実行の制御が一度定義されたメソッドに移り、計算が行なわれた後、再び呼出し側に戻ってきました。呼出し側では、メソッドを呼び出した部分の記述が、戻り値に置き換えられます。

図7-18 squareメソッドの呼出しと戻り値の返送

☆複雑なメソッドの呼出し

メソッドの呼出しは、式の中に記述できますので、実引数を示す式の中にも別のメソッドの呼出しを記述することができます。次のような複雑なメソッド呼出しでは、内側の実引数から順番に評価されていきます。従って、呼出しの順番もより内側に書かれているメソッドから行われることになります。以下の記述では、greater→square→printLineと実行されることになります。

	printLine(  "result:" +  square(  greater( 30,  40 ) - 10 ) )  );
		
実際に、この1文がどのように実行されるかを追ってみましょう。
1. greater( 30, 40 )が実行される→評価結果は40になる
2. square( 40 - 10 )が実行される→評価結果は900になる
3. printLine( "result: " + 900 );が実行される

☆戻り値のあるメソッドの命名方法

メソッドの名前は、小文字から始まり、動詞が使われるという不文律がありました。また、動詞+名詞でも構わず、その場合は名詞の部分は大文字から始まりました。更に戻り値のあるメソッドでは、使われる動詞にも一般的な傾向があります。情報を返すメソッドでは、getという動詞が用いられます。また、論理値を返すメソッドでは、isという動詞が多く名前の中に使われています。タートル・グラフィックスにあるメソッドの名前を少しだけ列挙してみました。
 情報を返す:  getColor, getFont, getName, getKey, getX, getY, getImage
 論理値を返す: isVisible, isTurtle, isMouseDown, isKeyDown, equals
自分で定義する際にも、メソッドに名前をつけるときにこのような慣習を守っておくと、後でプログラムを読み返したときにわかりやすいでしょう。

☆論理値を返すメソッドの例

戻り値を返すメソッドの中でも、論理値を返すメソッドの場合は、if文やwhile文などの条件を記述する式に用いられます。たとえば、現在の時刻がお昼の12時から14時の間であることを判別するためのメソッドを記述してみます。プログラムの初心者ならば次のようにプログラムするでしょう。論理値を返すので、戻り値の型としてはbooleanを指定しています。名前は、慣習に従って、動詞のisをつけています。現在の時刻を24時間として時を変数hourに求めています。
		
	boolean  isLunchTime(  )   {
		int  hour = getHour( );
		if ( hour >= 12 && hour < 14 ) {
			return true ;
		} else {
			return false;
		}
	}
		

ところが、少し慣れてくると、論理型の変数に条件式が直接代入できることがわかります。 条件式は、truefalseに評価されますので、それを変数に代入して、その値を返してあげれば良いわけです。

		
	boolean  isLunchTime(  )   {
		int  hour = getHour( );
		boolean condition = ( hour >= 12 && hour < 14 );
		return condition;
	}
		

最後には、論理型の変数も使わずに、直接return文の戻り値を返す式のところに条件式を記述するようになります。


	boolean   isLunchTime(  )   {
		int  hour = getHour( );
		return hour >= 12 && hour < 14;
	}
		
return文の戻り値の式は、条件式になっています。このように記述すると、条件式が評価されて、論理値のtrueあるいはfalseが返されることになります。今度は、このメソッドをstartメソッドなどの他のメソッドの中から呼び出して記述するような場合を考えてみましょう。

	if  (  isLunchTime(  )  == true )  {
		printLine(  "It is lunch time!  Shall we have lunch?" );
	}
		
ところが、呼出し側でもこのように論理値と等しいかどうか比較しなくても、論理値そのものを返してくるのですから、if文の括弧の中に メソッドの呼出しだけを直接記述することができます。比較結果は、どうせtruefalseという論理値に評価されることを 思い出して下さい。

	if  (  isLunchTime(  )  )  {
		printLine(  "It is lunch time!  Shall we have lunch?" );
	}
		
このように論理値を戻り値として返すメソッドを用いる場合は、メソッドの呼出しを直接if文の条件式の中に記述して使います。

☆実習7-11 greaterとlesser、averageを作る

大きい方の数を返すメソッド、小さい方の数を返すメソッド、2つの値の平均値を返すメソッドを それぞれ作成し、ユーザの入力に応じて表示させるようにしなさい。クラス名はPractice0711とします。

☆実習7-12 べき乗を計算するメソッドpowerを作る

べき乗を計算するメソッドを作りなさい。パラメータの入力は、基となる数と指数の2つで、いずれも整数型です。 戻り値の型は、整数型ですが、結構大きな数になるので、長桁整数(long)型にします。 ユーザの入力に応じて結果を表示させるようにしなさい。クラス名はPractice0712とします。

☆課題7-7 素数かどうかを求めるメソッドisPrimeを作る

パラメータで与えられた数(整数)が素数(その数と1以外では割り切れない数)かどうかを計算するメソッドを作りなさい。 戻り値の型は、論理型になります。このメソッドを繰返しの中で呼び出す形で、3から99までの 奇数について、素数かどうかを表示するプログラムを作りなさい。 クラス名はPrimeDisplayとします。

■戻り値のあるメソッドの例

☆整数や実数の二乗を求めるメソッド(多重定義のメソッド)

整数だけでなく、他の型のデータに対しても二乗を求めるメソッドを定義します。 ここで、同じ名前で整数用、長桁整数用、および実数用に二乗を求めるメソッドを定義します。


	// 整数用のsquare
	int  square( int x ) {  return x*x; }

	// 長桁整数用のsquare
	long square( long x ) { returnx*x; }

	// 実数用のsquare
	double square( double x ) { returnx*x; }
		

新たに定義されたこの3つのメソッドをstartメソッドから呼び出して使ってみます。 ただし、この3つのメソッドは、それが定義されたクラス(プログラム)の中だけしか有効でありません。 必要であれば、毎回定義するようにします。


	public void start( ) {
		int dars = square( 12 );
		long population = square( 6500000000L );
		double  radius = square(  6.5e12 );
	}
		

<<Previous Lecture >>Next Lecture