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
import java.io.*;    // BufferedReader

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
  
    XXXX() {
        double x,y;
        System.out.printf("平方根を求めます\n");
        x=getDouble("正の値(実数)を入力してください >> ");
        y=Math.sqrt(x);
        System.out.printf("%fの平方根は%fです。\n",x,y);
    }

    private double getDouble(String prompt){
        BufferedReader bffrd = new BufferedReader(new InputStreamReader(System.in));
        double ret=0.0;
        System.out.print(prompt);
        try {
            ret = Double.valueOf(bffrd.readLine()).doubleValue();
        }
        catch(IOException e) {
            System.out.println("IO Error");
            System.exit(1);
        }
        return ret;
    }
}

/****************** 実行結果 *********************
平方根を求めます
正の値(実数)を入力してください >> 3
3.000000の平方根は1.732051です。

平方根を求めます
正の値(実数)を入力してください >> 5
5.000000の平方根は2.236068です。
**************************************************/

メソッド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 void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String text1="Hello. Everyone.";
        String text2="";
        String text3="";
        int i;
        for (i=0; i<text1.length(); i++) {
            text2+=Character.toUpperCase(text1.charAt(i));
            text3+=Character.toLowerCase(text1.charAt(i));
        }
        System.out.println(text1);
        System.out.println(text2);
        System.out.println(text3);
    }
}

/******* 実行結果 ******
Hello. Everyone.
HELLO. EVERYONE.
hello. everyone.
************************/

メソッド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 {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
  
    XXXX() {
        double x=5.0;
        double y=8.0;
        double z=0.0;
        z=average(x,y);
        System.out.printf("%fと%fの平均値は%fです\n",x,y,z);
    }

    /*****************************
    2つの値の平均値を求めるメソッド
    入力:2つの実数引数p,q
    出力:メソッドの戻り値がp,qの平均値になっている
    ******************************/
    private double average(double p, double q) {
        double r;
        r=0.5*(p+q);
        return r;
    }
}


/********************* 実行結果 ********************
5.000000と8.000000の平均値は6.500000です
****************************************************/

説明

1.プログラムの実行はこれまで通り,XXXX()の先頭行から始まり,XXXX()の最終行で終わる

XXXX()の最初から始まり,
z=average(x,y);まで到達するところ

    XXXX() {
↓     double x=5.0;
     double y=8.0;
     double z=0.0;
      z
=average(x,y);
     
System.out.printf("%fと%fの平均値は%fです\n",x,y,z);
  }

  private double average(double p, double q) {
      double r;
      r=0.5*(p+q);
      return r;
  }

2.z=average(x,y);の実行は,次の2段階になる。
(1)average(x,y)で処理がメソッドdouble average(double p, double q)に移る。
ここで変数xの値がpに,変数yの値がqにコピーされる。
メソッド内では「r=0.5*(p+q);」が実行され,次に「return r;」でdouble型の値を持ち帰る。

    XXXX() {
      double x=5.0;
      double y=8.0;
      double z=0.0;
 ←    z=average(x,y);
↓     
System.out.printf("%fと%fの平均値は%fです\n",x,y,z);
 }

 private double average(double p, double q) {
 
      double r;
      r=0.5*(p+q);
      return r;
  }


(2)処理が元に戻り,持ち帰ったdouble型の値をzに代入する。

   XXXX() {
       double x=5.0;
       double y=8.0;
       double z=0.0;
 →    z=average(x,y);
     System.out.printf("%fと%fの平均値は%fです\n",x,y,z);
 }

↑  private double average(double p, double q) {
      double r;
     r=0.5*(p+q);
↑     return r;
 
}


3.メソッドの作り方(メソッドの定義)
(最初にprivateがつくが,ここではこれはおまじない。→クラス)
private
double average(double p, double q) {
        ↑       ↑    ↑
       double型   2つのdouble型の値を受け取る。
       の値     受け取りに用いる変数はpとq。
       で返す    
この例ではmain()から送られてくるxとyをpとqに受け取る。
              この受け取り変数のpとqは引数(仮引数)と呼ばれる
              また,送り側変数のxとyは引数(実引数)と呼ばれる。
    double r;
    r=0.5*(p+q); ←2つのdouble型の値の平均値を求める
    return r;  ←平均値(double型)を返す
}

4.メソッドについての注意事項
private double average(double p, double q) {
    double r;
    r=0.5*(p+q);
    return r;
}
このメソッドはdouble型の値を返すので,double型のメソッドと呼ばれる。
(このメソッドの型はdoubleであるという言い方もできる)
double average(double p, double q)」の「double」はメソッドの型を表す。
また,メソッドの型がdoubleなので,メソッドの終わりで値を返す意味で「return r;」を書くが
r」はメソッドの型と同じdouble型を使う。

5.先頭の「private」はしばらくはおまじないということにしておこう。
 「8.4 クラスのカプセル化」のところでprivateの意味が明らかになる。

xの値5.0,yの値8.0とした場合,
z=average(x,y)のところを実行する時,コンピュータが声を出したら次のようになるだろう。

mainさんの声
mainさんは専門職人に仕事を頼む人の役割である。

averageさんの声
(私の仕事は渡していただいた2つの数値の
平均値を求めて返すだけです。他のことを頼
まれてもできません。)
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に格納しましょう。




ほいきた。
xの値5.0とyの値8.0ですね。
それではpの値5.0とqの値8.0として受け取ります。

  :(計算は大変だ,でもこれは私の仕事)
計算結果は6.5です。

 

練習5.1

スナップショットを入れたプログラムである,動作を予想し確認しなさい。
スナップショットというのは実行のようすを確認するためにちょっと画面に表示をするためのprintfなどの出力のことである。
確認が終わったら,このprintf文はなくすか,コメント行にしてしまうなどの処置をすることになる。
そうしないと黙って仕事をする有能な専門職人ではなくなるからだ。
List 5.2.2 スナップショットを入れたメソッドaverage()

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
  
    XXXX() {
        double x=5.0;
        double y=8.0;
        double z;
        System.out.printf("x=%f y=%f\n",x,y);
        z=average(x,y);
        System.out.printf("%fと%fの平均値は%fです\n",x,y,z);
    }

    private double average(double p, double q) {
        double r;
        System.out.printf("p=%f q=%f\n",p,q);
        r=0.5*(p+q);
        System.out.printf("r=%f\n",r);
        return r;
    }
}

よくある誤りの例

(2)2値の最大公約数を求めるメソッド

2つの正整数を入力変数とし,最大公約数(正整数)を返すメソッドをつくり,検証用のmain()を作る。
「処理の繰り返し」の課題17に最大公約数を求める方法が書いてある。

List 5.2.3 メソッドcalculateGCD()

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int x=128;
        int y=72;
        int z;
        System.out.printf("x=%d y=%d\n",x,y);
        z=calculateGCD(x,y);
        System.out.printf("%dと%dの最大公約数は%dです\n",x,y,z);
    }

    /*****************************
    ユークリッドの互除法を用いて2つの
    正整数値の最大公約数値を求めるメソッド
    入力:2つの正整数引数p,q
    出力:メソッドの戻り値が最大公約数になっている
    ******************************/
    private int calculateGCD(int p,int q) {
        int r;
        r=p%q;
        while (r!=0) {
            p=q;q=r;
            r=p%q;
        }
        return q;
    }
}

/********************* 実行結果 ********************
x=128 y=72
128と72の最大公約数は8です
****************************************************/

説明

プログラムはXXXX()の先頭行から始まり,XXXX()の最終行で終わる。

メソッドの作り方(メソッドの定義)
private int calculateGCD(int p, int q) 行末に「;」がないことに注意
       ↑         ↑  ↑
       int型     2つのint型の値を受け取る。
       の値     受け取りに用いる変数はpとq。
       で返す    
main()から送られてくるxとyをpとqに受け取る。
              この受け取り変数のpとqは引数(仮引数)と呼ばれる
              また,送り側変数のxとyは引数(実引数)と呼ばれる。
{
    int r;
    r=p%q;
    while (r!=0) {
        p=q;q=r;
        r=p%q;
    }
    return q;   ←最大公約数値(int型)を返す intの値をreturnで返しているが,メソッドの型と一致している
}

練習5.2

スナップショットのprintfを入れたプログラムである,動作を予想し確認しなさい。

List 5.2.4 スナップショットのprintfを入れたメソッドcalculateGCD

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int x=128;
        int y=72;
        int z;
        System.out.printf("x=%d y=%d\n",x,y);
        z=calculateGCD(x,y);
        System.out.printf("%dと%dの最大公約数は%dです\n",x,y,z);
    }

    private int calculateGCD(int p,int q) {
        int r;
        r=p%q;
        System.out.printf("start p,q,r=%d,%d,%d\n",p,q,r);
        while (r!=0) {
            System.out.printf("loop p,q,r=%d,%d,%d >> ",p,q,r);
            p=q;q=r;
            r=p%q;
            System.out.printf("%d,%d,%d\n",p,q,r);
        }
        System.out.printf("return q=%d\n",q);
        return q;
    }
}


ここまでに出て来たメソッドに関する規則をまとめると次のようになる。
(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 {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int x=128;
        int y=72;
        int z;
        System.out.printf("x=%d y=%d\n",x,y);
        z=calculateGCD(x,y);
        System.out.printf("%dと%dの最大公約数は%dです\n",x,y,z);
    }

    private int calculateGCD(int x,int y) {
        int r;
        r=x%y;
        while (r!=0) {
            x=y;y=r;
            r=x%y;
        }
        return y;
    }
}

/********************* 実行結果 ********************
x=128 y=72
128と72の最大公約数は8です
****************************************************/


練習5.3

次のプログラムの動作を予想し確認しなさい。
List 5.2.7 メソッドgetNextNumber

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int i;
        int k;
        int count;
        for (i=10;i<15;i++) {
            count=0;
            k=i;
            System.out.printf("%d<%d>",k,count);
            while (k!=1) {
                k=getNextNumber(k);
                count++;
                System.out.printf("-%d<%d>",k,count);
                if (count%8==7) System.out.printf("\n ");
            }
            if (count%8!=7) System.out.printf("\n");
        }
    }

    private int getNextNumber(int x) {
        int ret;
        if (x%2==1) ret=x*3+1;
        else ret=x/2;
        return ret;
    }
}

 

 課題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 {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int i;
        for (i=0;i<7;i++) {
            putMultiChar('A',i);
            putMultiChar('c',6-i);
            System.out.printf("\n");
        }
    }

    /****************************************
    char chをnumber個連続して表示するメソッド
    返す値はない
    *****************************************/
    private void putMultiChar(char ch,int number) {
        int i;
        for (i=0;i<number;i++) {
            System.out.printf("%c",ch);
        }
    }
}

/********************* 実行結果 ********************
cccccc
Accccc
AAcccc
AAAccc
AAAAcc
AAAAAc
AAAAAA
****************************************************/


練習5.4

次のプログラムの動作を予想し確認しなさい。
List 5.3.2 メソッドputMultiChar

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int i;
        for (i=0;i<7;i++) {
            putMultiChar('A',i);
            putMultiChar('c',6-i);
            putMultiChar('c',6-i);
            putMultiChar('A',i);
            System.out.printf("\n");
        }
    }

    private void putMultiChar(char ch,int number) {
        int i;
        for (i=0;i<number;i++) {
            System.out.printf("%c",ch);
        }
    }
}

 

5.4 引数が配列のメソッド

引数が通常の変数の場合,メソッドが呼び出される時に,呼び出し側の変数がメソッドの引数に渡される。この時,値がコピーされ,新しい変数が誕生している。だからメソッド内でこの変数の値が変化しても,呼び出し側の変数に影響はない。(List 5.2.3参照)
しかし,引数が配列の場合は,メソッドが呼び出される時に,呼び出し側の変数がメソッドの引数に渡され,メソッドから戻る時に,メソッド内の配列は
呼び出し側の配列に書き戻される。すなわち,引数で渡された配列へのメソッド内での作業結果は呼び出し側の配列に反映する。
(この説明は不正確だが,理解しやすい。このように理解していて当面は差し支えない。実際は配列引数の場合は呼び出し側でもメソッド側でも同じ配列が見えているに過ぎない。)

ソートを行なうメソッドと配列を表示するメソッドを作ってみよう。メソッドsortArray()で操作した内容がXXXX()内の配列dataに反映されている。
メソッド内では有効なデータ個数を配列変数名.lengthで取得し,有効なデータに対して作業を行なっている。そのためこれらのメソッドは異なる配列要素数の配列に対しても,そのまま使うことが出来る。

List 5.4.1 配列を受け入れるメソッド

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        int[] data={
            76,21,39,85,34,54
        };
        printArray(data);
        sortArray(data);
        printArray(data);
    }

    /****************************************
    配列x[]の有効な要素を大きい順
    にソートするメソッド (返す値はない)
    *****************************************/
    private void sortArray(int[] x) {
        int i,j,tmp;
        int number;
        number=x.length;
        for (i=0;i<number-1;i++) {
            for (j=i+1;j<number;j++) {
                if (x[i]<x[j]) {
                    tmp=x[i];
                    x[i]=x[j];
                    x[j]=tmp;
                }
            }
        }
    }

    /****************************************
    配列x[]の有効な要素を表示する
    メソッド (返す値はない)
    *****************************************/
    private void printArray(int[] x) {
        int i;
        int number;
        number=x.length;
        for (i=0;i<number;i++) {
            System.out.printf("%5d",x[i]);
        }
        System.out.printf("\n");
    }
}

/********************* 実行結果 ********************
   76   21   39   85   34   54
   85   76   54   39   34   21
****************************************************/

配列引数の記述についての補足

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 文字列操作のメソッド

次の例では,文字列を受け取り,文字列を返すメソッドを扱っている。
引数で与えられた文字列中に連続したスペースがあったら1つのスペースに変更するメソッドである。

List 5.5.1 文字列操作メソッドの例

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String mystring1="We all  live    in a yellow submarine.";
        String mystring2;
        mystring2=shortenSpace(mystring1);
        System.out.println(mystring1);
        System.out.println(mystring2);
    }

    /****************************************
    与えられた文字列中の連続スペースを1つにまとめた文字列を返す
    *****************************************/
    private String shortenSpace(String in) {
        String out="";
        int i;
        int number;
        char ch;
        boolean foundspace=false;
        number=in.length();
        for (i=0;i<number;i++) {
            ch=in.charAt(i);
            if (foundspace){
                if (ch!=' ') {
                    out+=ch;
                    foundspace=false;
                }
            } else {
                out+=ch;
                if (ch==' ') {
                    foundspace=true;
                }
            }
        }
        return out;
    }
}

/********************* 実行結果 ********************
We all  live    in a yellow submarine.
We all live in a yellow submarine.
****************************************************/

boolean型の変数は,true,falseの2つの値しかとらない。条件の真偽を保存する変数である。

次のプログラム5.5.2は,引数で与えられた文字列に手を加えて,引数で結果を返そうとして失敗した例である。
文字列String型では結果をそのまま持ち帰ることは出来ないため,
5.5.1の様に作業結果は
メソッドの値として返さなければならない。

List 5.5.2 文字列操作メソッドの例(失敗例)

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String mystring="We all  live    in a yellow submarine.";
        System.out.println(mystring);
        shortenSpace(mystring);
        System.out.println(mystring);
    }

    /****************************************
    引数で与えられた文字列中の連続スペースを1つにまとめた文字列
    にして,引数で返したつもりだったけれど,呼び出し側ではその結
    果が見えない失敗例
    *****************************************/
    private void shortenSpace(String str) {
        String tmp=str;
        int i;
        int number;
        char ch;
        boolean foundspace=false;
        str="";
        number=tmp.length();
        for (i=0;i<number;i++) {
            ch=tmp.charAt(i);
            if (foundspace){
                if (ch!=' ') {
                    str+=ch;
                    foundspace=false;
                }
            } else {
                str+=ch;
                if (ch==' ') {
                    foundspace=true;
                }
            }
        }
    }
}

/
********************* 実行結果 ********************
We all  live    in a yellow submarine.
We all  live    in a yellow submarine.
*
**************************************************/


文字列操作のメソッドを自分で作ってみよう。
「java Stringクラス」でWeb検索すると多くのStringクラスの静的メソッドに関する説明が見つかる。
そのため,このようなメソッドは実際には作る意味は無いが,学習例題として取り上げる。

mystrlen(文字列) 与えられた文字列の文字数を返すメソッド。
mystrcat(文字列1,文字列2) 文字列2を文字列1の後に追加した文字列を返すメソッド。

List 5.5.3 文字列操作メソッドの例

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String string1="yellow";
        String string2="white";
        String string3;
        int len;
        len=mystrlen(string1); /*文字列の長さを得るメソッド*/
        System.out.printf("string1=%s len=%d\n",string1,len);
        len=mystrlen(string2); /*文字列の長さを得るメソッド*/
        System.out.printf("string2=%s len=%d\n",string2,len);
        string3=mystrcat(string1,string2); /*文字列追加コピーメソッド*/
        len=mystrlen(string3); /*文字列の長さを得るメソッド*/
        System.out.printf("string3=%s len=%d\n",string3,len);
    }

    /*文字列strの長さを得るメソッド*/
    private int mystrlen(String str) {
        int len;
        len=str.length();
        return len;
    }

    /*文字列str2をstr1の後ろに付け加えた文字列を返すメソッド*/
    private String mystrcat(String str1, String str2) {
        String retstr;
        retstr=str1+str2;
        return retstr;
    }
}


/********************* 実行結果 ********************
string1=yellow len=6
string2=white len=5
string3=yellowwhite len=11
****************************************************/

練習5.5

次のプログラムの動作を予想し確認しなさい。ヒント 「x--」は「x=x-1」と同じ意味である。
List 5.5.4 メソッドstrcpyrvs

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String string1="yellow";
        String string2;
        System.out.printf("string1=%s\n",string1);
        string2=strcpyrvs(string1);
        System.out.printf("string2=%s\n",string2);
    }

    private String strcpyrvs(String str) {
        String out="";
        int i;
        int num=str.length();
        for(i=num-1; 0<=i; i--) {
            out+=str.charAt(i);
        }
        return out;
    }
}

List 5.5.5 スナップショットをつけたメソッドstrcpyrvs

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        String string1="yellow";
        String string2;
        System.out.printf("string1=%s\n",string1);
        string2=strcpyrvs(string1);
        System.out.printf("string2=%s\n",string2);
    }

    private String strcpyrvs(String str) {
        String out="";
        int i;
        int num=str.length();
        System.out.printf("str=%s num=%d\n",str,num);
        for(i=num-1; 0<=i; i--) {
            out+=str.charAt(i);
            System.out.printf("out=%s\n",out);
        }
        return out;
    }
}

練習5.6

次のプログラムの実行結果を予想しなさい。
List 5.5.6 メソッドsearchChar

public class XXXX {
    public static void main(String[] args) {
        XXXX mainprg = new XXXX();
    }
 
    XXXX() {
        final int NO=-1;
        /*finalがついていると,この変数は変化しないの意味になる*/
        String str="question";
        char x;
        int found;
        System.out.println(str);
        x='e';
        found=searchChar(str,x);
        if (found!=NO) {
            System.out.printf("%c is at %d\n",x,found);
        } else {
            System.out.printf("%c doesn't exist\n",x);
        }
        x='a';
        found=searchChar(str,x);
        if (found!=NO) {
            System.out.printf("%c is at %d\n",x,found);
        } else {
            System.out.printf("%c doesn't exist\n",x);
        }
    }

    private int searchChar(String str, char ch) {
        int len=0,ret=-1,i;
        len=str.length();
        for (i=0; i<len; i++) {
            if (str.charAt(i)==ch) {
                ret=i;
            }
        }
        return ret;
    }
}

 

 課題5 その4

次のメソッドとチェック用プログラムを作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。

(6)文字列strの左からn文字を返すメソッド
private String left(String str, int n)
を作成しなさい。ただし
出来上がった状態で,メソッドleft内でprintfを用いてはならない。
もし文字列strの長さが足りなかったらあるだけ返すように作りなさい。    (p05ex06.java)

例えば
String str1="exercises";
の時
str2=left(str1,5); → str2には"exerc"が得られる。
str3=left(str1,10); → str3には"exercises"が得られる。
一般的にメソッドでは仕様にない機能はつけてはいけない。
(例えばメソッド内で画面表示してはならない。デバッグのためにメソッド内で画面表示した場合は,提出時には消しておく。)
次の検証用プログラムを使いなさい。

    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);

(8)文字列sの左からm文字目からn文字を返すメソッド

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段階で考える手順

オープニングメッセージでゲームの概要を説明する
{
  数当てゲームを1回行なう
}再挑戦する意向ならこれを繰り返す

だいぶ大雑把な手順であるが,これを次第に詳細化してゆけばよい。

第2段階で考える手順(「数当てゲームを1回行なう」の内容)

秘密の数を乱数で設定する(これは表示しない)
{
  画面に何回目のトライか表示させながらキーボードから数を得る
  得た数と秘密の数を比べて「大きすぎ」「小さすぎ」等を表示する
}得た数と秘密の数が一致していなかったらこれを繰り返す
何回で当たったか表示する


これでイメージが湧くようになった。しかし,現在が何回目かということを覚えておく必要があるので回数カウンタを追加する。

第2段階で考える手順(「数当てゲームを1回行なう」の内容)の更新

秘密の数を乱数で設定する(これは表示しない)
回数カウンタのクリア
{
  回数カウンタのカウントアップ
  画面に回数カウンタの値を表示させながらキーボードから数を得る
  得た数と秘密の数を比べて「大きすぎ」「小さすぎ」等を表示する
}得た数と秘密の数が一致していなかったらこれを繰り返す
何回で当たったか表示する(回数カウンタの値を表示すればよい)

ここまで来てもまだ細部は決まっていないが,細部はメソッドに任せることにして(有能なメソッド君が細部をやってくれることを期待して),一番大きな流れ(第1段階)と,2番目に大きな流れ(第2段階)をプログラムにしよう。

第1段階のプログラムへの翻訳

showOpenningMesage();/*オープニングメッセージの表示*/
do {
    playTheGame();/*1ゲーム行なう*/
} while (keepPlaying());/*もう一度やりたいなら繰り返す*/

メソッドshowOpenningMesage()はオープニングメッセージを表示する。引数もないし,メソッドの返す値もない。メソッドの型はvoidになる。

メソッドkeepPlaying()は,人間に再度トライしたいか問合せ,再度挑戦したいと返事がきたらtrue,そうでなかったらfalseを返すメソッドにすればよい。
だからこのメソッドはboolean型とする。どのようにしてこのようなメソッドを実現するかはまだ考えないでおく。

第2段階のプログラムへの翻訳(メソッドplayTheGame()の内容)

secret=generateTheSecret();/*秘密の数の設定*/
count=0;/*トライ回数クリア*/
do {
    count++;/*トライ回数カウントアップ*/
    number=getNumber(count);/*キーボードから数を得る*/
    judgeNumbers(number,secret);/*入力数と秘密数の当否と判定結果の表示*/
} while (number!=secret);/*不正解の間繰り返す*/
printf("%d 回で正解しました\n",count);

メソッドgenerateTheSecret()は秘密の数(1〜100)を発生する。発生した整数はメソッドの返す値としてもらうことにする。
だからこのメソッドはint型とする。どのようにしてこのようなメソッドを実現するかはまだ考えないでおく。
メソッドgetNumber(count)はキーボードから1〜100までの数を入力して(人間に入力させて),メソッドの値として返すことにする。
だからこのメソッドはint型とする。
また,課題では,何回目のトライかを表示することになっているので,引数としてトライ回数を与え,表示してもらうことにする。
どのようにしてこのようなメソッドを実現するかはまだ考えないでおく。
メソッドjudgeNumbers(number,secret)はキーボード入力値と秘密数を与えて,判定を表示してもらう。メソッドの型は値を返さないのでvoidになる。どのようにしてこのようなメソッドを実現するかはまだ考えないでおく。

ここまで考えるとcount,number,secretの3つはint型の変数であればよいと考えられる。そこで,これらの変数を宣言する。

int secret;/*秘密の数*/
int number;/*キーボードからもらった数*/
int count;/*トライ回数カウンタ*/

次に各メソッドを詳細化しよう。
まず,第1段階のプログラムで出てきたメソッドを3つ詳細化する。

List 5.7.1 メソッドshowOpenningMesage オープニングメッセージの表示(ゲームの説明)
引数はないので引数のところには何も書かない。
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");
}

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 再度ゲームを続けるかを問合せるメソッド
               再度挑戦したいということだったら1,そうでなかったら0を返すメソッド

詳細仕様
「もう一度挑戦するか」(期待回答は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)

{
    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);
    }
}

List 5.7.6 メソッドgenerateTheSecret 秘密数の生成

詳細仕様
1〜100の範囲の乱数(きまぐれに選んだ数)を発生させ,メソッドの値として返す。
乱数発生方法は次の2行となる。
        Random rnd = new Random();
        secretNumber=rnd.nextInt(100)+1;

nextInt()の検討
nextInt()は呼び出すたびにある範囲の1つの値を返すメソッドである。
nextInt(32768).....0〜32767の乱数
nextInt(100).......0〜99の乱数
nextInt(100)+1.....1〜100の乱数
なお,Randomを用いているので
import java.util.Random;
が必要となる。 乱数発生機構の使用例

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〜100の範囲で1つの秘密の数を持っているので当ててください。
あなたはキーボードからあなたの思った数を入力してください。
コンピュータはあなたが入力した数に対して判定を下します。
そして「大きすぎ」「小さすぎ」あるいは「正解」と答えます。
何回で正解できるか試してください。

1回目 1〜100の値を入力してください →50
50 は大きすぎます。
2回目 1〜100の値を入力してください →25
25 は大きすぎます。
3回目 1〜100の値を入力してください →12
12 は大きすぎます。
4回目 1〜100の値を入力してください →6
6 は小さすぎます。
5回目 1〜100の値を入力してください →9
9 は大きすぎます。
6回目 1〜100の値を入力してください →7
7 は小さすぎます。
7回目 1〜100の値を入力してください →8
8 正解です。
7 回で正解しました
もう一度挑戦しますか (Yes/No) →n

*********************************/

この例のように大きな流れから段階的詳細化を行なう設計方法は「トップダウン」とも呼ばれる。これに対し,必要となりそうなメソッドを先に作って,その仕様を明らかにしながら積み上げてゆく方法は「ボトムアップ」 と呼ばれる。例えば文字列操作メソッド群や文字操作メソッド群などの整備は「ボトムアップ」の考えで作成されたメソッド群である。実際の設計では,大きな 流れは「トップダウン」設計で行い,よく使われそうなメソッド群は「ボトムアップ」で作成される。そして「トップダウン」と「ボトムアップ」は交互に検討 され,メソッドの仕様が明らかになってゆく。

コラム getInt(), getString(), main(),XXXX()もメソッドである

ここでまでに多くのプログラムを書いてきたが,実はgetInt()やgetString()もメソッドであった。

実はmain()も値を返さないvoid型のメソッドである。しかもスタートアップルーチンから呼び出される唯一の,名前の変更の許されないメソッドである。

public static void main(String[] args)

よくみると,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


 

これ以降ではメソッドとチェック用プログラムを作り,メソッドの動作を検査しなさい。また複数の引数検査を行なうことになるが,検査の妥当性について検討を加えなさい。

(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);

(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");

(6)文字列strの左からn文字を返すメソッド
private String left(String str, int n)
を作成しなさい。ただし出来上がった状態で,メソッドleft内でprintfを用いてはならない。
もし文字列strの長さが足りなかったらあるだけ返すように作りなさい。    (p05ex06.java)

例えば
String str1="exercises";
の時
str2=left(str1,5); → str2には"exerc"が得られる。
str3=left(str1,10); → str3には"exercises"が得られる。
一般的にメソッドでは仕様にない機能はつけてはいけない。
(例えばメソッド内で画面表示してはならない。デバッグのためにメソッド内で画面表示した場合は,提出時には消しておく。)
次の検証用プログラムを使いなさい。
    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);


(8)文字列sの左からm文字目からn文字を返すメソッド
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);

 

(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」および 乱数発生機構の使用例 を参考にしなさい。
次のプログラムでこのメソッドを検査しなさい。

    String aNumber;
    int count;

    for (count=0; count<10; count++) {

       
aNumber=generateNumber();
        System.out.printf("%s\n",aNumber);

    }

 

(14)ヒットアンドブロー数あてゲームを作りなさい。  (p05ex14.java)
ヒットアンドブロー数あてゲームとは,次のようなゲームです。
(1)コンピュータが4桁の数を文字列として乱数より作成します。ただし,この文字列は「ヒットアンドブローゲーム有効文字列」です。
(2)人間はキーボードより4桁の数字を文字列として与えます。メソッドgetStringで取り込めばよいでしょう。ただし,有効な「ヒットアンドブローゲーム有効文字列」かどうかの検査を行ない,不正だったら再入力させます。
(3)コンピュータは自分の保持する4桁数字と人間が入力した4桁数字を比較し,完全一致文字数(ヒット数)と不完全一致文字数(ブロー数)を画面に表示します。
(4) この値を見ながら,人間はキーボードより次の4桁の数字を文字列として与えます。コンピュータは再び完全一致文字数(ヒット数)と不完全一致文字数(ブ ロー数)を画面に表示します。この作業の繰り返しの後,人間は4桁の数字すべてを当てることが出来ます。すなわち完全一致文字数が4なったらゲームは終了 です。何回の試行で当てられたか画面に表示してください。
(7回から10回くらいの試行で当てることが出来るでしょう)

 

次の課題は発展問題(挑戦課題です。力をつけたい人は挑戦してください)


(15)コンピュータ対人間の双方向ヒットアンドブロー数あてゲームを作りなさい。  (p05ex15.java)
双方向型では,コンピュータ,人間の双方が4桁の数を相手にわからないように保持し,この値をお互いに当てるようになります。
人間が4桁数字を当てようと攻撃します。この時,コンピュータが完全一致文字数(ヒット数)と不完全一致文字数(ブロー数)を答えます。
次にコンピュータが4桁数字を当てようと攻撃します。この時,人間が完全一致文字数(ヒット数)と不完全一致文字数(ブロー数)を答えます。
交互にこの作業を行い,先に当てた方が勝ちです。強い攻撃アルゴリズムを持たせてください。