5.メソッド
Copyright(C) 19Jan2003 coskx
このページでは静的メソッドについて解説している。
本来はクラスの構成部分としてメソッドがあるが,メソッドの概念を習得することにポイントを置いている
ここで修得してほしい内容は以下の通りである。
1.ライブラリメソッドの存在を理解し,ライブラリメソッドの概要を調べる方法を知る。
2.自分で簡単なメソッドを作れる。
3.メソッドの仕様を明確に説明できる。
4.引数の値の受け渡し(呼び出し側が与えた値が引数にコピーされる)の様子を理解する
5.メソッドの返す値の受け渡し方(return
値)を理解する。
6.段階的詳細化プログラミングについて理解する。
このページでの内容は以下を含んでいる。
5.1 ライブラリメソッド
5.2 自分で作るメソッド
5.3 値を返さないメソッド
5.4 引数が配列のメソッド
5.5 文字列操作のメソッド
5.6 段階的詳細化プログラミング1
5.7 段階的詳細化プログラミング2
5.1 ライブラリの利用
(1)平方根を計算するメソッド
コンピュータで平方根を計算するプログラムを書くことを考えよう。自分で平
方根を計算するプログラムを作るのは,すこし面倒なことである。平方根を計算する作業は,すでに組み込まれているメソッド(C言語でいうところの関数)にお願いするの
が得策である。次のプログラムで紹介するのは,「実数を1つ受け取ってその平方根を計算してその答えを返すメソッドMath.sqrt()」である。
sqrt(x)はxの平方根を求めるメソッドなので,「y=Math.sqrt(x);」は「メソッドMath.sqrtにxの平方根を求めてもらい,その値をyに代入しなさい」の意味になる。Math.sqrt(x)は「xの平方根」を意味しているので,次のようにも使える。
printf("%fの平方根は%fです。\n",5.0,Math.sqrt(5.0));
printf("%fの平方根は%fです。\n",x,Math.sqrt(x));
List 5.1.1 メソッドMath.sqrt() |
import java.util.*; // StringTokenizer 平方根を求めます |
メソッドMath.sqrt()という名前は,正しくはMathクラスの静的(static)メソッド「public static double sqrt(double
var)」である。
その仕様は「double型の変数varに値を受け取り,平方根を求め,double型の値で返す。」である。
このようにメソッドはひとかたまりの作業を請け負うプログラム単位であり,その仕様は明確である。
メソッドに与えられる変数(定数のこともある)のことを引数と呼ぶ。
例えばy=Math.sqrt(x)の引数はxである。
その他にもsin,cosなどのメソッドがある。
例えばdouble x,yのとき,
y=Math.sin(x);
y=Math.cos(x);
のように使える。
xの値が3の時,y=Math.sqrt(x)のところを実行する時,コンピュータが声を出したら次のようになるだろう。
mainさんの声 | Math.sqrtさんの声 (私の仕事は渡していただいた数値の平方根を 求めて返すだけです。他のことを頼まれてもで きません。) |
y=sqrt(x)のsqrt(x)の部分を実行しましょう。 sqrtさん,xの値3をあげるから, (その平方根を計算して,)計算結果を 私に渡してください。 ありがとう,計算結果は1.732051ですね。 y=sqrt(x)のy=の部分を実行しましょう。 それではこれ(1.732051)をyに格納しましょう。 |
ほいきた。 3の平方根ですね。 :(計算は大変だ,でもこれは私の仕事) 計算結果は1.732051です。 |
メソッドは専門職人のようなもので,特定の面倒なことを頼めばやってくれる職人である。
ここでは与えた数値に対する平方根を求めてくれる専門職人である。
「java Mathクラス」でWeb検索すると多くのMathクラスの静的メソッドに関する説明が見つかる。
メソッドMath.sqrt()は数学分野のメソッドであったが,次「(2)小文字を大文字にするメソッド」に出てくるように数学分野でないメソッドもたくさんある。
(2)小文字を大文字にするメソッド
次のプログラムで紹介するのは,2つの文字に関するメソッドである。
(1)public static char toUpperCase(char ch):文字コードを1つ受け取って,もしその文字コードが小文字だったら大文字に変換し大文字の文字コードを返し,それ以外なら,受け取った文字コードをそのまま返すメソッド
(2)public static char toLowerCase(int ch):文字コードを1つ受け取って,もしその文字コードが大文字だったら小文字に変換し小文字の文字コードを返し,それ以外なら,受け取った文字コードをそのまま返すメソッド
この2つは,正しくはCharacterクラスの静的(static)メソッドである。
(英語でuppercaseというのは大文字,lowercaseは小文字の意味である。)
Character.toUpperCase('a') → 'A'
小文字なので大文字に変換される
Character.toUpperCase('b') → 'B' 小文字なので大文字に変換される
Character.toUpperCase('C') → 'C'
小文字ではないのでそのまま
Character.toUpperCase('+') → '+' 小文字ではないのでそのまま
Character.toUpperCase(ch)はchの大文字変換を求めるメソッドなので,「ych=Character.toUpperCase(ch);」は「chの大文字変換を求め,その値をychに代入しなさい」の意味になる。Character.toUpperCase(ch)は「chの大文字変換」を意味しているので,次のようにも使える。
printf("%cの大文字変換は%cです。\n",ch,Character.toUpperCase(ch));
printf("%cの大文字変換は%cです。\n",'h',Character.toUpperCase('h'));
List 5.1.2 ライブラリメソッドtoupper()とtolower() |
public class XXXX { |
「メソッドpublic static char toUpperCase(char ch)」の仕様は「char型の変数chに値を受け取り,chを文字コードと解釈してその文字が小文字だったら大文字の文字コードを求め,そうでなかったらchの値をそのまま保ち,char型の値で返す。」である。
ここでもメソッドが文字コードをうまく変換してくれる専門職人としての役割を果たしているのがわかる。
「java Characterクラス」でWeb検索すると多くのCharacterクラスの静的メソッドに関する説明が見つかる。
課題5 その1 |
---|
(1)数学メソッドのMath.sin(),Math.cos()を用いてsin cos表を1度刻みで作りなさい。 (p05ex01.java)
ヒントMath.sin(),Math.cos()メソッドの引数は単位は度ではなくradである。
πの値は
3.14159265358979 を使う。
Math.sin()とMath.cos()はdouble型の値を与えるとdouble型の値を返してくるメソッドである。
deg sin cos
0 0.00000000 1.00000000
1 0.01745241 0.99984770
2 0.03489950 0.99939083
3 0.05233596 0.99862953
: : :
86 0.99756405 0.06975647
87 0.99862953 0.05233596
88 0.99939083 0.03489950
89 0.99984770 0.01745241
90 1.00000000 0.00000000
5.2 自分で作るメソッド
(1)2値の平均値を求めるメソッド
ここまでに見たメソッドは,「あるものを受け取って,加工して,何かを返す」作業していた。自分でこのようなメソッドを作ってみよう。ここでは「2つの実数型の値を受け取って,平均値を求めて,それをdouble型の値で返すメソッドdouble
average(double p, double
q)」を作ってみる。メソッドの名前averageは本来自分で自由につけてよいが,名が中身をあらわすようにしよう。
自分で作るメソッドもある機能を果たす専門職人として振舞っている。
メソッドを使う立場からすると,メソッドの中身がどのように作ってあるかは問題ではなく,何を引数として与えて,何が得られるのかのみが関心事である。
メソッドに何を与えて,何を得ることができるのかを明らかに文書化したものを,メソッドの仕様(スペック,specification)と呼ぶ。
メソッドを使うと,大きなプログラムでも見通しの良いプログラムを作成することができる。
List 5.2.1 自分で作ったメソッドaverage() | |||
public class XXXX { | |||
説明 | |||
1.プログラムの実行はこれまで通り,XXXX()の先頭行から始まり,XXXX()の最終行で終わる。
2.z=average(x,y);の実行は,次の2段階になる。
4.メソッドについての注意事項 5.先頭の「private」はしばらくはおまじないということにしておこう。 |
xの値5.0,yの値8.0とした場合,
z=average(x,y)のところを実行する時,コンピュータが声を出したら次のようになるだろう。
mainさんの声 mainさんは専門職人に仕事を頼む人の役割である。 |
averageさんの声 |
z=average(x,y)のaverage(x,y)の部分を実行しましょう。 averageさん,xの値5.0とyの値8.0をあげるから, (その平均値を計算して,)計算結果を 私に渡してください。 ありがとう,計算結果は6.5ですね。 z=average(x,y)のz=の部分を実行しましょう。 それではこれ(6.5)をzに格納しましょう。 |
|
練習5.1 |
List 5.2.2 スナップショットを入れたメソッドaverage() | |
public class XXXX { |
|
(2)2値の最大公約数を求めるメソッド
2つの正整数を入力変数とし,最大公約数(正整数)を返すメソッドをつくり,検証用のmain()を作る。
「処理の繰り返し」の課題17に最大公約数を求める方法が書いてある。
List 5.2.3 メソッドcalculateGCD() |
public class XXXX { /********************* 実行結果 ******************** |
説明 |
プログラムはXXXX()の先頭行から始まり,XXXX()の最終行で終わる。 |
練習5.2 |
スナップショットのprintfを入れたプログラムである,動作を予想し確認しなさい。
List 5.2.4 スナップショットのprintfを入れたメソッドcalculateGCD | |
public class XXXX { |
ここまでに出て来たメソッドに関する規則をまとめると次のようになる。
(1)メソッド定義の第1行は「型名(int とか double など)+メソッドの名前(自由につけてよいが,名が中身を表すようにつける)+カッコの中に受け取り用変数を並べた定義」の形で書く。
(2)メソッド定義の第1行の最初の型名は,メソッドの返す値の型を示すが,これは「メソッドの型」と呼ばれる。
−−メソッドaverage()のメソッドの型はdoubleである。
−−メソッドcalculateGCD()のメソッドの型はintである。
(3)メソッド定義の第1行のカッコの中の受け取り用変数は引数と呼ばれる。
(4)メソッド定義の終わりには「return + ある変数」があるが,メソッドの型とこの変数の型は一致していなければならない。
−−メソッドaverage()のメソッドの型はdoubleであるので,変数rはdouble型である。
−−メソッドcalculateGCD()のメソッドの型はintであるので,変数qはint型である。
(5)XXXX()中でメソッドを利用している場所の記述は「メソッド呼び出し」と呼ばれる。メソッド呼び出し時にカッコ中に変数または定数を書くが,その個数は,メソッド定義の引数の個数と一致し,かつ,メソッド定義の引数の型と一致していなければならない。
(6)メソッド内で変数p,qの値が変化しても,List5.2.3のように,呼び出し側の変数x,yは変化しない。
(3)メソッド内で定義された変数
呼び出し側とメソッド定義内で,同じ名前の変数があったとしても,これは無関係である。
ここでは呼び出し側の変数x,yとメソッドcalculateGCD(int
x,int
y)内の変数x,yはまったく別の変数である。
よって,メソッド内の変数x,yの値は変化してゆくが,呼び出し側の変数x,yは初期値(128と72)を保っている。
List 5.2.5 同じ変数名がmain()とメソッド内にある例 |
public class XXXX { /********************* 実行結果 ******************** |
練習5.3 |
List 5.2.7 メソッドgetNextNumber | |
public class XXXX { |
課題5 その2 |
---|
次のメソッドとチェック部(呼び出し側)を作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。
(2)4つのdouble型変数を受け取り,その平均値を返すメソッド
private double
average4(double x1, double x2, double x3, double
x4)
をつくり,このメソッドが正しく動作するかどうかテストしなさい。 (p05ex02.java)
p05ex02() {
double
ans;
ans=average4(10.0, 20.0, 30.0,
40.0);
System.out.printf("%f\n",ans);
}
このテストプログラムで25.0が表示されたらよい。
出来上がった状態で,メソッドaverage4()中にprintf文を入れてはいけない。
デバッグのために制作途中でprintf文をメソッド内で使うことは差し支えない。
(3)ヘロンの公式は,三角形の三辺の長さから,その三角形の面積を求める公式である。三角形を構成する三辺をa,b,cとした時,
s=(a+b+c)/2とし,P=(s-a)(s-b)(s-c)sを求めると,面積SはPの正の平方根で求められる。
三辺の長さa,b,cを与えてその三角形の面積を返すメソッド
private double
heronformula(double a,double b,double c)
を作成しなさい。 (p05ex03.java)
ただし,a,b,cに2.0,3.0,6.0のように三角形を構成できない3つの数が与えられた場合はPが負になってしまい計算不能になるので,このような場合は値0.0をメソッドの値として返すこととする。次のようなメソッド検査メインプログラムで検証しなさい。
検証するというのは,コンパイルエラーが出ずに,何らかの出力が得られるかどうかを見るのではなく,別の考え方で手計算で行なった計算と一致したかどうかを検査することである。
出来上がった状態で,メソッドheronformula()中にprintf文を入れてはいけない。デバッグのために制作途中でprintf文を使うことは差し支えない。
double
a,b,c,area;
a=3.0;
b=4.0;
c=5.0;
area=heronformula(a,b,c);
System.out.printfprintf("a,b,c,area=%f, %f,
%f, %f\n",a,b,c,area);
a=3.0;
b=4.0;
c=8.0;
area=heronformula(a,b,c);
System.out.printfprintf("a,b,c,area=%f, %f,
%f, %f\n",a,b,c,area);
5.3 値を返さないメソッド |
---|
数学のメソッドは,計算結果である値を必ず返すように出来ている。しかし値を返 さないメソッドも存在する。値を返さないメソッドはむしろ「メソッド呼び出し」と呼ばずに,「機能呼び出し」という呼び方の方がイメージしやすい。次の例は,引数で与 えられた文字を与えられた回数連続表示するメソッド(あるいは機能)である。値を返さないメソッドの型は「void」である。voidメソッドではメソッドの最後に「return+値;」の部分がない。
List 5.3.1 値を返さないメソッドの例 |
public class XXXX { |
練習5.4 |
List 5.3.2 メソッドputMultiChar | |
public class XXXX { |
5.4 引数が配列のメソッド
引数が通常の変数の場合,メソッドが呼び出される時に,呼び出し側の変数がメソッドの引数に渡される。この時,値がコピーされ,新しい変数が誕生している。だからメソッド内でこの変数の値が変化しても,呼び出し側の変数に影響はない。(List
5.2.3参照)
しかし,引数が配列の場合は,メソッドが呼び出される時に,呼び出し側の変数がメソッドの引数に渡され,メソッドから戻る時に,メソッド内の配列は呼び出し側の配列に書き戻される。すなわち,引数で渡された配列へのメソッド内での作業結果は呼び出し側の配列に反映する。
(この説明は不正確だが,理解しやすい。このように理解していて当面は差し支えない。実際は配列引数の場合は呼び出し側でもメソッド側でも同じ配列が見えているに過ぎない。)
ソートを行なうメソッドと配列を表示するメソッドを作ってみよう。メソッドsortArray()で操作した内容がXXXX()内の配列dataに反映されている。
メソッド内では有効なデータ個数を配列変数名.lengthで取得し,有効なデータに対して作業を行なっている。そのためこれらのメソッドは異なる配列要素数の配列に対しても,そのまま使うことが出来る。
List 5.4.1 配列を受け入れるメソッド |
public class XXXX { |
配列引数の記述についての補足
List 5.4.1では配列引数を
private void printArray(int[] x)
のように表したが,
private void printArray(int x[])
のような書き方も許されている。
課題5 その3 |
---|
次のメソッドとチェック部(呼び出し側)を作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。
(4)int型の配列arrayと配列内の有効データ数sizeを与えると,配列array内の平均値をdouble型で戻すメソッド
private double
average(int array[], int size)
を作り,動作を検査しなさい。ただし,メソッド内でprintfを使用してはならない。検査するためのprintfはmain()内に記述しなさい。
(p05ex04.java)
動作の検査に当たっては,次のことを示しなさい。
1) 1,2,3,4,5,6,7,8,9,10の10個の値を使い,平均値が5.5になること
2)
1,12,23,34,45,56の6個の値を使い,平均値が28.5になること
(5)int型の配列arrayを与えると,配列array内のデータの並び順が逆になって戻ってくるメソッド
private void
reverseArray(int[] array)
を作り,動作を次に与えるプログラムで検査しなさい。
ただし,出来上がった状態で,メソッドreverse()中にprintf文を入れてはいけない。
デバッグのために制作途中でprintf文をメソッド内で使うことは差し支えない。
(p05ex05.java)
int i;
int[] array = {1,2,3,11,12,13,21,22,23,100};
for(i=0; i<array.length; ++i) System.out.printf("%d ",array[i]);
System.out.printf("\n");
reverseArray(array);
for(i=0; i<array.length; ++i) System.out.printf("%d ",array[i]);
System.out.printf("\n");
5.5 文字列操作のメソッド |
---|
List 5.5.1 文字列操作メソッドの例 |
public class XXXX { |
boolean型の変数は,true,falseの2つの値しかとらない。条件の真偽を保存する変数である。
List 5.5.2 文字列操作メソッドの例(失敗例) |
public class XXXX { /**************************************** |
mystrlen(文字列)
与えられた文字列の文字数を返すメソッド。
mystrcat(文字列1,文字列2)
文字列2を文字列1の後に追加した文字列を返すメソッド。
List 5.5.3 文字列操作メソッドの例 |
public class XXXX { |
練習5.5 |
List 5.5.4 メソッドstrcpyrvs | |
public class XXXX { |
|
List 5.5.5 スナップショットをつけたメソッドstrcpyrvs | |
public class XXXX { |
練習5.6 |
List 5.5.6 メソッドsearchChar | |
public class XXXX { |
課題5 その4 |
---|
次のメソッドとチェック用プログラムを作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。
(6)文字列strの左からn文字を返すメソッドString str1="exercises";
String str2;
String str3;
System.out.println(str1);
str2=left(str1,5);
System.out.println(str2);
str3=left(str1,10);
System.out.println(str3);
(7) 文字列sの右からn文字を返すメソッド
private String right(String
str, int
n)
を作成しなさい。
ただし出来上がった状態で,メソッドright内printfを用いてはならない。
もし文字列sの長さが足りなかったらあるだけコピーするように作る。
(p05ex07.java)
例えば
String str1="exercises";
の時
str2=right(str1,5);
→ str2には"cises"が得られる。
str3=right(str1,10);
→ str3には"exercises"が得られる。
次の検証用プログラムを使いなさい。
String str1="exercises";
String str2;
String str3;
System.out.println(str1);
str2=right(str1,5);
System.out.println(str2);
str3=right(str1,10);
System.out.println(str3);
private String mid(String
str, int m, int
n)
を作成しなさい。
m文字目というのは,1から数えることとする。(例えばabcdefgの4文字目はdである)
ただしメソッドmid内でprintfを用いてはならない。
もし文字列sの長さが足りなかったらあるだけコピーするように作りなさい。
また,もし文字列sの文字数がm未満だったら,コピーすべき文字列がないので,
空の文字列""返しなさい。
(p05ex08.java)
例えば
String str1="exercises";
の時
str2=mid(str1,2,5);
→ str2には"xerci"が得られる。
str3=mid(str1,2,10);
→ str3には"xercises"が得られる。
str4=mid(str1,12,3); → str4は空の文字列になる。
次の検証用プログラムを使いなさい。
String str1="exercises";
String str2;
String str3;
String str4;
System.out.println(str1);
str2=mid(str1,2,5);
System.out.println(str2);
str3=mid(str1,2,10);
System.out.println(str3);
str4=mid(str1,12,3);
System.out.println(str4);
System.out.printf("%d\n",str4.length());
(9)文字列sを逆順に並べ替えて文字と文字の間にスペースを入れて返すメソッド
private String reverseString(String s)
を作成しなさい。ただしメソッドreverseString内でprintfを用いてはならない。
(p05ex09.java)
例えば
String str="exercises";
の時
str1=reverseString(str);
→ str1は"s e s i c r e x e"となる。
ここに示した例を検査するプログラムを作って検査しなさい。
System.out.printf("%s\n",str);
str1=reverseString(str);
System.out.printf("[%s]\n",str1);
5.6 段階的詳細化プログラミング1 |
---|
メソッドについて学んだが,メソッドを使用しても,プログラムを追跡するのが大変になっただけだと感ずる人も多いと思う。
し
かし,ある仕様の作業を行なう時,小さな仕事に細分化し,細分化された仕事をメソッドにお願いするようにプログラムを書けば,プログラミングがすっきりする。
すなわち,おおまかな作業の流れを考えることと,細部の作業を別々に考えるプログラミングを修得することにより,大きなプログラムを書くのが容易になる。
メソッドを利用した段階的詳細化プログラミングがプログラムを書く時に考え方を整理するのに役立つ。
段階的詳細化プログラミングで課題に挑戦 1 |
++++++*++++++ +++++***+++++ ++++*****++++ +++*******+++ ++*********++ +***********+ ************* +***********+ ++*********++ +++*******+++ ++++*****++++ +++++***+++++ ++++++*++++++ |
この図形を描く時に,すべて自分で書くとしたら次のようになるであろう。
「+」を6個,「*」を 1個,「+」を6個描き,改行 「+」を5個,「*」を 3個,「+」を5個描き,改行 「+」を4個,「*」を 5個,「+」を4個描き,改行 「+」を3個,「*」を 7個,「+」を3個描き,改行 「+」を2個,「*」を 9個,「+」を2個描き,改行 「+」を1個,「*」を11個,「+」を1個描き,改行 「+」を0個,「*」を13個,「+」を0個描き,改行 「+」を1個,「*」を11個,「+」を1個描き,改行 「+」を2個,「*」を 9個,「+」を2個描き,改行 「+」を3個,「*」を 7個,「+」を3個描き,改行 「+」を4個,「*」を 5個,「+」を4個描き,改行 「+」を5個,「*」を 3個,「+」を5個描き,改行 「+」を6個,「*」を 1個,「+」を6個描き,改行 |
さらにint型変数 jとforループの構造を用いれば
for (j=6 ;0<=j; j--) { 「+」をj個,「*」を(13-2*j)個,「+」をj個描き,改行 } for (j=1 ;j<=6; j++) { 「+」をj個,「*」を(13-2*j)個,「+」をj個描き,改行 } |
ここでもし,メソッドprivate void printMultiChar(char ch,int
number)があって,文字chを続けてnumber個書いてくれるとすれば,あとは簡単だろうと考えられる。
for (j=6 ;0<=j; j--) { printMultiChar('+',j); printMultiChar('*',13-2*j); printMultiChar('+',j); System.out.printf("\n"); } for (j=1 ;j<=6; j++) { printMultiChar('+',j); printMultiChar('*',13-2*j); printMultiChar('+',j); System.out.printf("\n"); } |
そして,次のようなメソッドを作ればプログラミングは簡単になる。
メソッドstatic void printMultiChar(int ch,int
number)は与えられた文字(文字コードch)をnumber個続けて表示する。
具体的には次のように作ればよい。
private void printMultiChar(char ch,int number) { int j; for (j=0;j<number;j++) System.out.printf("%c",ch); } |
public class XXXX { public static void main(String[] args) { XXXX mainprg = new XXXX(); } XXXX() { int j; for (j=6 ;0<=j; j--) { printMultiChar('+',j); printMultiChar('*',13-2*j); printMultiChar('+',j); System.out.printf("\n"); } for (j=1 ;j<=6; j++) { printMultiChar('+',j); printMultiChar('*',13-2*j); printMultiChar('+',j); System.out.printf("\n"); } } private void printMultiChar(char ch,int number) { int j; for (j=0;j<number;j++) System.out.printf("%c",ch); } } |
段階的詳細化プログラミングで課題に挑戦 2 |
10000から10100までの素数を求めてみよう。
もし,「ある正整数がメソッドに与えられ,その数が素数だったらtrue,そうでなかったらfalseを返すメソッド」があれば,この課題は簡単に解けると考えることができる。
さらに詳細化して
「メソッドprivate boolean
isPrimenumber(int
number)は,与えられた整数numberが素数だったらtrue,そうでなかったらfalseを返す。」
と考えて,このメソッドを作ってみよう。(primenumberは素数の意味である)
このメソッドを作る上での考え方は,次のようになる。
「number
を2で割り切れるかどうか試し,numberを3で割り切れるかどうか試し,numberを4で割り切れるかどうか試し,numberを5で割り切れるか
どうか試し,.....を行ない,最初に割り切れる数がnumberだったらnumberは素数である。」
(例えば13は最初に割り切れる整数が13なので素数であり,91は最初に割り切れる整数が7なので素数ではない)
具体的には次のように作ればよい。
private boolean isPrimenumber(int number) {
int
j;
boolean ret;
j=2;
while
(number%j!=0) j++;
if (j==number) ret=true;/*素数だった*/
else ret=false;
/*素数ではなかった*/
return
ret;
}
boolean を返すことだけ考えれば,次のようにも書くことができる
private boolean isPrimenumber(int number) {
int
j;
j=2;
while
(number%j!=0) j++;
return (j==number);
}
このメソッドが使える前提で10000から10100までの素数を求めてみよう。5個見つかるごとに改行して,最後に見つかった個数を書くことにしよう。
すると次のようになる。
public class XXXX {
public static void main(String[] args) {
XXXX mainprg = new XXXX();
}
XXXX() {
int j,counter;
counter=0;
for (j=10000 ;j<=10100; j++) {
if (isPrimenumber(j)) {
counter++;
System.out.printf("%8d",j);
if (counter%5==0) System.out.printf("\n");
}
}
if (counter%5!=0) System.out.printf("\n");
System.out.printf("%d個見つかりました\n",counter);
}
private boolean isPrimenumber(int number) {
int j;
boolean ret;
j=2;
while (number%j!=0) j++;
if (j==number) ret=true;/*素数だった*/
else ret=false; /*素数ではなかった*/
return ret;
}
}
/**************実行結果******************
10007 10009 10037 10039
10061
10067 10069
10079 10091 10093
10099
11個見つかりました
****************************************/
この例のように段階的詳細化を展望しながら便利なメソッドを積み上げてゆく設計方法は「ボトムアップ設計」と呼ばれる。
ボトムアップ設計も,複雑で大きなプログラムを作る際の有力な武器である。
5.7 段階的詳細化プログラミング2 |
---|
メソッドを利用した「ボトムアップ設計」「トップダウン設計」の段階的詳細化プログラミングが大きなプログラムを書く時に大きな「ごりやく」となる。すこし大きなプログラムに挑戦する。
挑戦課題:数当てゲーム
コンピュータが乱数で発生した1〜100までの数(この数は表示されないので人間は知ること が出来ない)を人間が何回もトライして当てようとするゲームである。人間がトライで入力した値に対して,コンピュータは「大きすぎる」,「小さすぎる」と 表示する。(その結果人間は7回程度で正解にたどり着くことが出来る。)コンピュータは何回目のトライかを表示し,正解にたどり着いた時には,何回のトラ イで正解したのか表示することにする。また1ゲーム終わるごとに再挑戦するかどうか問合せるものとする。 |
第1段階で考える手順 |
オープニングメッセージでゲームの概要を説明する |
だいぶ大雑把な手順であるが,これを次第に詳細化してゆけばよい。
第2段階で考える手順(「数当てゲームを1回行なう」の内容) |
秘密の数を乱数で設定する(これは表示しない) |
第2段階で考える手順(「数当てゲームを1回行なう」の内容)の更新 |
秘密の数を乱数で設定する(これは表示しない) |
ここまで来てもまだ細部は決まっていないが,細部はメソッドに任せることにして(有能なメソッド君が細部をやってくれることを期待して),一番大きな流れ(第1段階)と,2番目に大きな流れ(第2段階)をプログラムにしよう。
第1段階のプログラムへの翻訳 |
showOpenningMesage();/*オープニングメッセージの表示*/ |
第2段階のプログラムへの翻訳(メソッドplayTheGame()の内容) |
secret=generateTheSecret();/*秘密の数の設定*/ |
次に各メソッドを詳細化しよう。
まず,第1段階のプログラムで出てきたメソッドを3つ詳細化する。
List 5.7.1 メソッドshowOpenningMesage オープニングメッセージの表示(ゲームの説明) 引数はないので引数のところには何も書かない。 |
private void showOpenningMesage() |
{ |
List 5.7.2 メソッドplayTheGame 1ゲーム行なう |
private void playTheGame() |
{ int secret;/*秘密の数*/ int number;/*キーボードからもらった数*/ int count;/*トライ回数カウンタ*/ secret=generateTheSecret();/*秘密の数の設定*/ count=0;/*トライ回数クリア*/ do { count++;/*トライ回数カウントアップ*/ number=getNumber(count);/*キーボードから数を得る*/ judgeNumbers(number,secret);/*入力数と秘密数の当否と判定結果の表示*/ } while (number!=secret);/*不正解の間繰り返す*/ System.out.printf("%d 回で正解しました\n",count); } |
List 5.7.3 メソッドkeepPlaying 再度ゲームを続けるかを問合せるメソッド |
詳細仕様 「もう一度挑戦するか」(期待回答はYesまたはNo)を表示する。 人間からの回答文字列を取り込み,先頭文字が'Y'または'y'ならメソッドの返す値を1に相でなかったら0にする。 ただし,YesとかNoと回答文字列が得られても困らないようにしておく。 |
private boolean keepPlaying() |
{ String buff; boolean ret=false; buff=getString("もう一度挑戦しますか (Yes/No) →"); if (buff.charAt(0)=='Y'||buff.charAt(0)=='y') ret=true; return ret; } |
次に,第2段階のプログラムで出てきたメソッドを3つ詳細化する。
List 5.7.4 メソッドgetNumber キーボードから1〜100の数値を受け取る(チェック機能付き) 呼び出し側から,引数でトライ回数を受け取る |
詳細仕様 飛び出し側から受け取ったトライ回数を表示する。 キーボードから数を受け取る。 その際,人間が数以外をキーボードから入力したり,1〜100以外の数を入力しても困らないようにし,再入力してもらう。 正当な範囲の値が入力されるまで再入力させるためにdoneという変数を導入する。 doneは,正当な範囲の値が入力されるまではfalse,入力されたらtrueになる変数である。 (このような変数はフラグと呼ばれている) |
private int getNumber(int count) |
{ int number; boolean done=false; do { System.out.printf("%d回目",count); number=getInt(" 1~100の値を入力してください →"); if (0<number && number<=100) done=true; } while (done==false); return number; } |
List 5.7.5 メソッドjudgeNumbers 秘密数とキーボード入力数の比較と判定表示 |
詳細仕様 秘密数secretとキーボード入力数numberを呼び出し側から受け取り,その2つの変数を比較し「小さすぎ」「大きすぎ」「正解」を表示する。 |
private void judgeNumbers(int number, int secret) |
{ |
List 5.7.6 メソッドgenerateTheSecret 秘密数の生成 |
詳細仕様 |
private int generateTheSecret() |
{ int secretNumber; Random rnd = new Random(); secretNumber=rnd.nextInt(100)+1; /*これで1〜100の乱数となる*/ return secretNumber; } |
ここまでの検討をまとめると次のプログラムが出来上がる。このようにおおまかな設計からはじまり,次第に詳細なメソッドの作成に至るプログラミング手法を「段階的詳細化プログラミング」と呼ぶ。
List 5.7.7 数当てゲームのできあがり |
/* GuessNumber.java */ import java.util.*; // StringTokenizer import java.io.*; // BufferedReader import java.util.Random; public class GuessNumber { public static void main(String[] args) { GuessNumber mainprg = new GuessNumber(); } GuessNumber() { showOpenningMesage();/*オープニングメッセージの表示*/ do { playTheGame();/*1ゲーム行なう*/ } while (keepPlaying());/*もう一度やりたいなら繰り返す*/ } private int getInt(String prompt){ BufferedReader bffrd = new BufferedReader(new InputStreamReader(System.in)); int ret=0; System.out.print(prompt); try { ret = Integer.valueOf(bffrd.readLine()).intValue(); } catch(IOException e) { System.out.println("IO Error"); System.exit(1); } return ret; } private String getString(String prompt){ BufferedReader bffrd = new BufferedReader(new InputStreamReader(System.in)); String ret=new String(); System.out.print(prompt); try { ret = bffrd.readLine(); } catch(IOException e) { System.out.println("IO Error"); System.exit(1); } return ret; } private void showOpenningMesage() { System.out.printf("**************************** 数当てゲーム ****************************\n"); System.out. printf("コンピュータが1?100の範囲で1つの秘密の数を持っているので当ててください。\n"); System.out.printf("あなたはキーボードからあなたの思った数を入力してください。\n"); System.out. printf("コンピュータはあなたが入力した数に対して判定を下します。\n"); System.out. printf("そして「大きすぎ」「小さすぎ」あるいは「正解」と答えます。\n"); System.out.printf("何回で正解できるか試してください。\n\n"); } private void playTheGame() { int secret;/*秘密の数*/ int number;/*キーボードからもらった数*/ int count;/*トライ回数カウンタ*/ secret=generateTheSecret();/*秘密の数の設定*/ count=0;/*トライ回数クリア*/ do { count++;/*トライ回数カウントアップ*/ number=getNumber(count);/*キーボードから数を得る*/ judgeNumbers(number,secret);/*入力数と秘密数の当否と判定結果の表示*/ } while (number!=secret);/*不正解の間繰り返す*/ System.out.printf("%d 回で正解しました\n",count); } private boolean keepPlaying() { String buff; boolean ret=false; buff=getString("もう一度挑戦しますか (Yes/No) →"); if (buff.charAt(0)=='Y'||buff.charAt(0)=='y') ret=true; return ret; } private int getNumber(int count) { int number; boolean done=false; do { System.out.printf("%d回目",count); number=getInt(" 1~100の値を入力してください →"); if (0<number && number<=100) done=true; } while (done==false); return number; } private void judgeNumbers(int number, int secret) { if (number<secret) { System.out.printf("%d は小さすぎます。\n",number); } else if (secret<number) { System.out.printf("%d は大きすぎます。\n", number); } else { System.out.printf("%d 正解です。\n", number); } } private int generateTheSecret() { int secretNumber; Random rnd = new Random(); secretNumber=rnd.nextInt(100)+1; /*これで1?100の乱数となる*/ return secretNumber; } } /************実行結果************ 1回目 1〜100の値を入力してください →50 |
この例のように大きな流れから段階的詳細化を行なう設計方法は「トップダウン」とも呼ばれる。これに対し,必要となりそうなメソッドを先に作って,その仕様を明らかにしながら積み上げてゆく方法は「ボトムアップ」
と呼ばれる。例えば文字列操作メソッド群や文字操作メソッド群などの整備は「ボトムアップ」の考えで作成されたメソッド群である。実際の設計では,大きな
流れは「トップダウン」設計で行い,よく使われそうなメソッド群は「ボトムアップ」で作成される。そして「トップダウン」と「ボトムアップ」は交互に検討
され,メソッドの仕様が明らかになってゆく。
コラム getInt(), getString(), main(),XXXX()もメソッドである |
ここでまでに多くのプログラムを書いてきたが,実はgetInt()やgetString()もメソッドであった。 よくみると,mainは文字列の配列を引数にしているメソッドである。 |
課題5 |
---|
(1)数学メソッドのMath.sin(),Math.cos()を用いてsin cos表を1度刻みで作りなさい。(再掲) (p05ex01.java)
ヒントMath.sin(),Math.cos()メソッドの引数は単位は度ではなくradである。
πの値は
3.14159265358979 を使う。
Math.sin()とMath.cos()はdouble型の値を与えるとdouble型の値を返してくるメソッドである。
deg sin cos
0 0.00000000 1.00000000
1 0.01745241 0.99984770
2 0.03489950 0.99939083
3 0.05233596 0.99862953
: : :
86 0.99756405 0.06975647
87 0.99862953 0.05233596
88 0.99939083 0.03489950
89 0.99984770 0.01745241
90 1.00000000 0.00000000
これ以降ではメソッドとチェック用プログラムを作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。
(10)文字列sが「ヒットアンドブローゲーム有効文字列」であるかどうかを判定するメソッド
private boolean checkString(String str)
を作りなさい。 (p05ex10.java)
ある文字列がヒットアンドブローゲーム有効文字列である場合は以下の3ルールをすべて満たす。
1)文字は4文字のみで構成されている。
2)文字はすべて数字だけで出来ている。(「0123456789」のみで出来ている)
3)4つの文字は互いに異なっている。
このメソッドは文字列sが「ヒットアンドブローゲーム有効文字列」であればtrue,そうでなければfalseをメソッドの値として返すものとする。
ただしメソッドcheckString内でstrで始まるライブラリメソッドおよびprintfを用いてはならない。
次に示す6つの例を使って検査するmainを作って検査しなさい。書式を実行例のように整えて検証しなさい。
String s1="1234"
String s2="123"
String s3="12345"
String s4="A123"
String s5="123#"
String s6="1231"
実行結果は書式を整えて次の例のようになるはずである。
1234 =>
0
123 => 1
12345 => 1
A123 =>
1
123# => 1
1231 => 1
ヒント
1)「char型変数mycharの値が数字の文字コードであるなら」は
if
('0'<=mychar&&mychar<'9') {
2)「4つの変数a,b,c,dの値が互いに異なる」というのは「a≠bかつb≠cかつc≠d」ではなく「a≠bかつa≠cかつa≠dかつb≠cかつb≠dかつc≠d」である。
(11)2つの文字列(前問のヒットアンドブローゲーム有効文字列)s1,s2を受け取り,完全一致文字数を返すメソッド
private int countHit(String
s1, String s2)
を作りなさい。このメソッドの内部にprintfを用いてはならない。 (p05ex11.java)
完全一致とはs1とs2の同じ位置に同じ文字が現れる場合である。
例えば"1234","9837"では3のみが完全一致なのでメソッドの返す値は1である。
"1234","9817"では完全一致は無いのでメソッドの返す値は0である。
"1234","1837"では1と3のみが完全一致なのでメソッドの返す値は2である。
"1234","1834"では1と3と4のみが完全一致なのでメソッドの返す値は3である。
"1234","1234"では4つの文字が完全一致なのでメソッドの返す値は4である。
次に示す例を使って検査するmainを作って検査しなさい。
String s0="1234";
String s1="9837", s2="9817", s3="1837", s4="1834",
s5="1234";
なお,画面表示では 「1234 9837 => 1」 のように表示させなさい。
(12)2つの文字列(前問のヒットアンドブローゲーム有効文字列)s1,s2を受け取り,不完全一致文字数を返すメソッド
private int
countBlow(String s1, String s2)
を作りなさい。このメソッドの内部にprintfを用いてはならない。 (p05ex12.java)
不完全一致とはs1とs2の異なる位置に同じ文字が現れる場合である。
例えば"1234","9387"では3のみが不完全一致なのでメソッドの返す値は1である。
"1234","9807"では不完全一致は無いのでメソッドの返す値は0である。
"1234","0391"では1と3のみが不完全一致なのでメソッドの返す値は2である。
"1234","4213"では1と3と4のみが不完全一致なのでメソッドの返す値は3である。
"1234","4312"では4つの文字が不完全一致なのでメソッドの返す値は4である。
完全一致を不完全一致として数えてはいけない。
次に示す例を使って検査するmainを作って検査しなさい。
String s0="1234";
String s1="9387", s2="9807", s3="0391", s4="4213",
s5="4312"
なお画面表示では 「1234 9387 => 1」 のように表示させなさい。
(13)前問の「ヒットアンドブローゲーム有効文字列」を生成するメソッド
private String generateNumber()
を作りなさい。このメソッドの内部にprintfを用いてはならない。 (p05ex13.java)
このメソッドの内部では乱数発生メソッドを用いて「ヒットアンドブローゲーム有効文字列」候補を作り,先に作成されているメソッド
private int checkString(String
s)
をこのメソッド内で使って,検査し,検査を通過するまで,新たな候補を生成すること。
メソッドrand()については「List5.7.6」および 乱数発生機構の使用例
を参考にしなさい。
次のプログラムでこのメソッドを検査しなさい。
(14)ヒットアンドブロー数あてゲームを作りなさい。 (p05ex14.java)
ヒットアンドブロー数あてゲームとは,次のようなゲームです。
(1)コンピュータが4桁の数を文字列として乱数より作成します。ただし,この文字列は「ヒットアンドブローゲーム有効文字列」です。
(2)人間はキーボードより4桁の数字を文字列として与えます。メソッドgetStringで取り込めばよいでしょう。ただし,有効な「ヒットアンドブローゲーム有効文字列」かどうかの検査を行ない,不正だったら再入力させます。
(3)コンピュータは自分の保持する4桁数字と人間が入力した4桁数字を比較し,完全一致文字数(ヒット数)と不完全一致文字数(ブロー数)を画面に表示します。
(4)
この値を見ながら,人間はキーボードより次の4桁の数字を文字列として与えます。コンピュータは再び完全一致文字数(ヒット数)と不完全一致文字数(ブ
ロー数)を画面に表示します。この作業の繰り返しの後,人間は4桁の数字すべてを当てることが出来ます。すなわち完全一致文字数が4なったらゲームは終了
です。何回の試行で当てられたか画面に表示してください。
(7回から10回くらいの試行で当てることが出来るでしょう)
次の課題は発展問題(挑戦課題です。力をつけたい人は挑戦してください) |