手続き

今回の内容

今回はプログラム中の命令の列を「手続き」としてひとまとめにする方法を紹介します。現在練習で書いているプログラムは100行にも満たない小さなものばかりですが、実際に世の中で利用されているプログラムの中にはその数百万倍の規模のものもあります。

そのような巨大なプログラムを書く場合には、様々な方法でプログラムを分割して小さなプログラムの部品を作成します。これらの部品は個別にプログラミングされ、さらにそれらを組み合わせて大きなプログラムを構成していくのが普通です。

図: 分割統治

情報科学の分野では、このように「ばらばらの物をまとめて、1つのものとして取り扱う」という考え方が非常に重要です。どんなに大きなプログラムでも、小分けにして部品としてまとめられていれば、それらを使うのは簡単です。現に、これまで使用してきた「メッセージダイアログを表示する」などは1~2行で書ける内容ですが、その裏側では数万行の命令が組み合わさって実現しています。しかし、皆さんは気にすることなくJOptionPane.showMessageDialogという「メソッド」を使ってメッセージダイアログを表示させるという結果だけを利用できます。

今回は、Javaの「メソッド」という仕組みを利用してプログラムを分割し、それらを再利用できるようにする方法を紹介します。メソッドによって部品を作り出し、それらの部品を組み合わせてより複雑で大きなプログラムを作成できるようになります。

今回の作業

今回の例題、練習問題、課題は以下のパッケージに作成してください。

パッケージの名前
j1.lesson04

クラスの作成方法については、「クラスを作成する」を参照してください。

メソッド

プログラムの分割

まずは、メソッドを利用したプログラムを見てみましょう。下記のプログラムを実行させて結果を確認してください。

リスト: FirstMethod
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class FirstMethod {
 
    public static void main(String[] args) {
        new FirstMethod().start();
    }
 
    void start() {
        greeting();
        showQuiz();
    }
 
    void greeting() {
        String name = JOptionPane.showInputDialog("名前をどうぞ");
        JOptionPane.showMessageDialog(null, name + "さん、ようこそ");
    }
 
    void showQuiz() {
        String input = JOptionPane.showInputDialog("10 + 20 = ?");
        int answer = Integer.parseInt(input);
        if (answer == 30) {
            JOptionPane.showMessageDialog(null, "正解です");
        }
        else {
            JOptionPane.showMessageDialog(null, "不正解です");
        }
    }
}

これを実行すると、まず1回目の授業で紹介したような「名前を聞いてあいさつをする」という処理が実行され、その後に前回の「クイズを出して正解か不正解か表示する」という処理が実行されています。この2つの処理はそれぞれ別のプログラムとしてこれまでに作成してもらいましたが、今回はこれらを「メソッド」で部品化して、部品ごとに起動しています。

プログラムを見てみましょう。まずはいつも通り、プログラム本体を記述していた部分です。「greeting();」と「showQuiz();」という見慣れない命令が書かれていますが、これらはプログラムの部品であるメソッドを起動するための書き方です。greeting, showQuizはそれぞれ起動するメソッドの名前を表しています。

リスト: FirstMethod(抜粋)
void start() {
    greeting();
    showQuiz();
}

そして、これまでプログラム本体の命令を書いていた場所の外側に、「名前を聞いてあいさつをする」という別のプログラム(の断片)が書かれています。

リスト: FirstMethod(greeting)
void greeting() {
    String name = JOptionPane.showInputDialog("名前をどうぞ");
    JOptionPane.showMessageDialog(null, name + "さん、ようこそ");
}

ただし、void start() {…}ではなく、void greeting() {…}というように「start」の代わりに「greeting」という名前が使われています。これがまさに「メソッド」で、「名前を聞いてあいさつをする」という一連の命令を、greetingという名前で部品化しています。これを「メソッドの宣言」と呼びます。

同様に、showQuizという名前のメソッドも宣言しています。このメソッドに含まれている命令は、前回のクイズを出題するというプログラムと全く同じです。

リスト: FirstMethod(showQuiz)
void showQuiz() {
    String input = JOptionPane.showInputDialog("10 + 20 = ?");
    int answer = Integer.parseInt(input);
    if (answer == 30) {
        JOptionPane.showMessageDialog(null, "正解です");
    }
    else {
        JOptionPane.showMessageDialog(null, "不正解です");
    }
}

もういちど、void start() {…}の部分を見てみましょう。void start() {…}もgreetingやshowQuizと同じ形式で書かれていることがわかるでしょうか。これまでの授業でたびたび書いてきたvoid start() {…}も、実態はstartという名前のメソッド宣言です。これらのメソッドは、プログラムの末尾にある } よりも手前に宣言します。

このstartメソッドの中では、先ほど紹介したgreetingメソッドとshowQuizメソッドを起動しています。このように、メソッド名の後に()を付け、末尾に ; (セミコロン)と打てば、その名前のメソッドが起動します。個々のメソッドの中身は今まで通り{ … }の中に書いてある命令を上から順に実行していきます。この{…}という部分は、if文のときと同様に「ブロック」と呼び、複数の命令を書けます。

リスト: FirstMethod(start)
void start() {
    greeting();
    showQuiz();
}

それぞれのメソッドは次のような順番で起動されています。

図: メソッド起動の順番(1)

もちろん、startメソッドから起動するメソッドの順番を変えれば、処理の実行順序も変わります(試してみてください)。

ここまでを簡単にまとめてみましょう。プログラムを分割する方法の一つに「メソッド」というものがありますが、これは次のような形で宣言できます。

void <メソッドの名前>() {
    <命令>
}

この{ <命令> }の部分をブロックと呼び、複数の命令を書けます。このブロックに書いてある命令を処理させたい場合には、次のような命令でメソッドを起動してやります。

<メソッドの名前>();

メソッドの名前には、先ほど宣言したメソッドのうち、起動したいものを指定します。例では、greetingやshowQuizやstartなどの名前でメソッドを宣言し、startメソッドのブロックからgreeting, showQuizメソッドを順番に起動しました。

練習: メソッドの複数回の起動

作成するクラスの名前
Echo3

下記のプログラムを変更して、入力された文字列をそのまま表示するechoという名前のメソッドを宣言し、それを3回起動するプログラムを作成しなさい。

リスト: Echo3
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class Echo3 {
 
    public static void main(String[] args) {
        new Echo3().start();
    }
 
    void start() {
 
    }
 
}

「入力された文字列をそのまま表示する」という一連の命令は次のように書けます。

リスト: 入力された文字列をそのまま表示する
String input = JOptionPane.showInputDialog("メッセージをどうぞ");
JOptionPane.showMessageDialog(null, input);

これを中身に持つメソッドechoを宣言して利用してください。

メソッドと引数(ひきすう)

次のプログラムを見てみましょう。

リスト: OutOfScope
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class OutOfScope {
 
    public static void main(String[] args) {
        new OutOfScope().start();
    }
 
    void start() {
        String input = JOptionPane.showInputDialog("文字列を入力");
        showMessage();
    }
 
    void showMessage() {
        JOptionPane.showMessageDialog(null, input);
    }
}

このプログラムにはエラーがあり、実行できません。

エラーは下記の個所で、inputという名前の変数を利用できないために発生しています。

リスト: OutOfScope(エラー箇所)
void showMessage() {
    JOptionPane.showMessageDialog(null, input);
}

このinputという変数は、showMessageメソッドとは別の、startメソッドで宣言されています。

リスト: OutOfScope(start)
void start() {
    String input = JOptionPane.showInputDialog("文字列を入力");
    showMessage();
}

このように、変数は宣言したメソッドと異なるメソッドで利用できません。より詳しくいうと、宣言した変数を使用できる範囲(有効な範囲)は、それを宣言したブロック({…})の内部に限られます。上記の例ではinputという変数を宣言したのはstartメソッドのブロック内ですが、それをshowMessageメソッドのブロック内で利用できません。このような変数を使用できる範囲のことを「変数のスコープ」と呼びます。

図: 変数のスコープ

プログラム中で計算した値を異なるメソッドで利用するには、メソッドの「引数(ひきすう)」という仕組みが便利です。次のプログラムを見てみましょう。

リスト: ShowArgument
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class ShowArgument {
 
    public static void main(String[] args) {
        new ShowArgument().start();
    }
 
    void start() {
        String input = JOptionPane.showInputDialog("文字列を入力");
        showMessage(input);
    }
 
    void showMessage(String message) {
        JOptionPane.showMessageDialog(null, message);
    }
}

これは先ほどのエラーがあるプログラムとほとんど同じですが、2か所だけ変更があります。1か所目は、showMessageメソッドの宣言で、メソッド名の直後に「(String message)」という「仮引数(かりひきすう)の宣言」を含めている点です。

リスト: ShowArgument(showMessage)
void showMessage(String message) {
    JOptionPane.showMessageDialog(null, message);
}

仮引数とは、メソッドが起動した際に最初から利用できる変数のようなものです。変数同様に型と名前を書いて宣言し、命令の中ではその名前を指定して値を利用できます。このため、showMessageメソッドの内部では仮引数messageを使ってダイアログにメッセージを表示しています。

2か所目の変更点は、startメソッドからshowMessageメソッドを起動する際に、「showMessage(input)」のように仮引数に記憶させる値を指定している点です。この例では入力された値を記憶させたinputという変数を利用していますが、リテラルやそれらの計算結果なども指定できます。ここで指定した値がshowMessageメソッドに渡され、showMessageメソッドの中で仮引数 (このプログラムではmessage) を通してそれらの値を利用できます。なお、ここに指定する値を「実(じつ)引数(ひきすう)」とも呼びます。

リスト: ShowArgument(start)
void start() {
    String input = JOptionPane.showInputDialog("文字列を入力");
    showMessage(input);
}

このように、引数の仕組みを利用するとメソッド間で値をやり取りするようなプログラムを書けます。この引数付きのメソッドを利用するには、メソッド宣言に含まれる(…)の部分に次のような形式で仮引数を宣言します。

void <メソッドの名前>(<仮引数の型> <仮引数の名前>) {
    <命令>
}

このメソッドを起動するには、メソッドを起動する命令に含まれる(…)の部分に実引数を指定します。

<メソッドの名前>(<実引数>);

仮引数を含むメソッドを起動すると、それぞれの仮引数に実引数の値を代入してから処理が行われます。仮引数は変数のように扱えますので、起動されたメソッドの中で仮引数の名前を指定し、実引数に指定された値を利用できます。

図: 引数を伴うメソッドの起動

練習: メソッドと引数

作成するクラスの名前
SalesTax

入力した価格の税込価格を表示するプログラムを作りなさい。ただし、入力した価格を整数の実引数として受け取り、入力した価格と税込み価格を順に表示するメソッドを宣言して利用すること。

なお、メソッドの名前は「showTotal」、引数の名前は「price」など、わかりやすい名前を付けるのがよい。(あとで読んでもわかりやすければ別の名前でもかまわない。)

複数の引数をとるメソッド

次に、複数の引数を利用するようなメソッドについてみてみましょう。

リスト: QuizMethod
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class QuizMethod {
 
    public static void main(String[] args) {
        new QuizMethod().start();
    }
 
    void start() {
        showQuiz("10 + 20 = ?", 30);
        showQuiz("リンゴの色は? (1) 青 (2) 赤", 2);
        showQuiz("うるう年の1年の日数は?", 366);
    }
 
    void showQuiz(String message, int correct) {
        String input = JOptionPane.showInputDialog(message);
        int answer = Integer.parseInt(input);
        if (answer == correct) {
            JOptionPane.showMessageDialog(null, "正解です");
        }
        else {
            JOptionPane.showMessageDialog(null, "不正解です");
        }
    }
}

これはクイズを出題するプログラムですが、「実際にクイズを出題する」という部分はメソッドとして部品化しています。下記のshowQuizはそのクイズを出題するためのメソッドですが、クイズの問題文(String message)と、正解の数値(int correct)という2つの仮引数を宣言しています。

リスト: QuizMethod(showQuiz)
void showQuiz(String message, int correct) {
    String input = JOptionPane.showInputDialog(message);
    int answer = Integer.parseInt(input);
    if (answer == correct) {
        JOptionPane.showMessageDialog(null, "正解です");
    }
    else {
        JOptionPane.showMessageDialog(null, "不正解です");
    }
}

仮引数を2つ以上宣言する場合には、上記のようにそれぞれの仮引数を , (カンマ)で区切ります。同様に、このメソッドを起動するには2つの実引数が必要になりますが、こちらも2つの実引数をカンマで区切って指定します。

リスト: QuizMethod(start)
void start() {
    showQuiz("10 + 20 = ?", 30);
    showQuiz("リンゴの色は? (1) 青 (2) 赤", 2);
    showQuiz("うるう年の1年の日数は?", 366);
}

このプログラムでは、showQuizが3回起動されていますので、3問のクイズが出題されています。それぞれの起動で仮引数に設定される値をまとめると、次のようになります。

String message int correct
1回目の起動 10 + 20 = ? 30
2回目の起動 リンゴの色は? (1) 青 (2) 赤 2
3回目の起動 うるう年の1年の日数は? 366
図: メソッド起動の順番(2)

まとめると、複数の仮引数を伴うメソッドは、次の形式で宣言できます。

void <メソッドの名前>(<仮引数1>, <仮引数2>, …) {
    <命令>
}

このメソッドを起動するには、次のような形式で命令を書きます。

<メソッドの名前>(<実引数1>, <実引数2>, …);

練習: 複数の引数をとるメソッド

作成するクラスの名前
Arithmetic

2つの実数を受け取って、それらの加減乗除(+, -, *, /)の結果を表示するメソッドを宣言しなさい。

startメソッドからは、宣言したメソッドを異なる値の組み合わせで2回起動すること。

なお、これもメソッドの名前を「ShowResults」などわかりやすいものとすること。ただし、「2つの実数」については名前をつけにくいので、数学のように「a」「b」など付けるとよい。

プログラムの構造化

最後に、プログラムをさらに細分化する例を見てみましょう。

リスト: Recipe1
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package j1.lesson04;
 
import javax.swing.JOptionPane;
 
public class Recipe1 {
 
    public static void main(String[] args) {
        new Recipe1().start();
    }
 
    void start() {
        prepare();
        make();
        serve();
    }
 
    void prepare() {
        prepareMeat();
        prepareVegetable();
    }
 
    void prepareMeat() {
        work("肉を一口大に切る");
        work("肉に塩コショウを振る");
    }
 
    void prepareVegetable() {
        work("玉ねぎの皮をむき、スライスする");
        work("ニンジンの皮をむき、一口大に切る");
        work("ジャガイモの皮をむき、一口大に切る");
    }
 
    void make() {
        fry();
        boil();
    }
 
    void fry() {
        work("鍋を温め、バターを引く");
        work("鍋にスライスした玉ねぎを入れ、炒める");
        work("鍋にカットした肉を入れ、炒める");
    }
 
    void boil() {
        work("鍋にニンジンとジャガイモを加え、浸るまで水を加える");
        work("野菜の灰汁をとる");
        work("鍋にカレー粉を加える");
        work("しばらく煮込む");
    }
 
    void serve() {
        work("ご飯にカレーを盛る");
    }
 
    void work(String operation) {
        JOptionPane.showMessageDialog(null, operation);
    }
}

これは料理の手順をメソッドの起動や、ダイアログの表示等で表わしたものです。私たちも普段何かを考えるときには、最初から最も細かい手順の列で考えるのではなく、それらをいくつかにまとめ上げたものを段階的に細分化しているように思えます。

図: メソッド起動の順番(3)

このように、実際の手続きを大まかな構造に分解し、分解したものをさらに小さな構造に分解していくようなプログラミングの方法を「構造化プログラミング」と呼びます。構造化プログラミングは、人間が理解しやすい単位に分解するためプログラムが読みやすくなり、また規模の大きなプログラムを作成するときに分業しやすくなる、など様々な利点があります。

まとめ

今回は、命令の列を意味のある単位で「メソッド」として切り出して、プログラムを分割する方法について紹介しました。

メソッドを利用するには、まずメソッドを次の形で宣言します。

void <メソッドの名前>(<仮引数1>, <仮引数2>, …) {
    <命令>
}

このメソッドに含まれる{ <命令> }の部分をブロックと呼びます。このブロックに含まれる命令はいくつでもよく、それぞれ上から順に処理されます。これらの命令を実行するには、該当するメソッドを下記の形式で起動します。

<メソッドの名前>(<実引数1>, <実引数2>, …);

すると、同じ名前を持つメソッドが起動し、それぞれの仮引数には対応する実引数の値が記憶されます。仮引数は通常の変数のように扱えますので、メソッドを起動する際に渡された値を、起動先のメソッドで利用できます。

以上が今回のまとめです。「プログラムを分割し、部品として再利用できる」というようなプログラムを書けるようになりました。