[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

コンピュータIIIB(Javaアルゴリズム)第6回

目次
6.1 参照とメソッド
6.1.1 メソッドの基本
6.1.2 値呼出し
6.1.3 インスタンスの共有
6.1.4 参照呼出し
6.1.5 参照とメソッドの例
6.2 演習6
6.3 レポート課題
6.4 参考文献
索引
値呼出し   仮引数   関数   共有   参照呼出し   実引数   手続き   引数   メソッド  

6.1 参照とメソッド

6.1.1 メソッドの基本

関数function ) とは、呼び出されるとまとまった計算をして、呼び出し側に計算結果を返すプログラム単位です。 手続きprocedure ) とは、呼び出されるとまとまった処理をして、呼び出し側に戻るプログラム単位です。 Javaでは、関数も手続きも メソッドmethod ) で実現します。

メソッドには、インスタンス・メソッドとクラス・メソッドがあります。 今日は、(同じクラスの)クラス・メソッドのみを扱います。

メソッドは、定義してから呼び出します。 メソッド定義の形式は、関数の場合は

static クラス名 メソッド名 (クラス名 変数名, ...) {
    文の並び
}

となり、手続きの場合は

static void メソッド名 (クラス名 変数名, ...) {
    文の並び
}

となります。 メソッド呼出しの形式は、関数の場合は式として

メソッド名(式, ...)

となり、手続きの場合は文として

メソッド名(式, ...);

となります。

6.1.2 値呼出し

メソッドの括弧の中は、 引数argument ) と呼ばれます。 メソッドと呼び出し側は、引数を使ってデータをやり取りします。

メソッド呼出しの括弧の中は、 実引数actual argument ) と呼ばれ、メソッド定義の括弧の中は、 仮引数formal argument ) と呼ばれます。 Javaでは、メソッドが呼び出されると、仮引数の変数が用意され、そこに実引数の式の値が格納されます。 このように呼び出すことを、 値呼出しcall by value ) と言います。

メソッドを呼び出したとき、引数が基本データ型ならば、仮引数に基本型の値がコピーされるだけなので、呼び出し側のデータに影響はありません。 一方、引数が参照データ型ならば、仮引数に参照が格納されるので、呼び出し側のデータに影響を与えられます。 この違いを理解することが、今日の目標です。

次の例は、呼び出し側のデータ x = 100 をメソッドの変数 n に渡すものです。 この場合は成功します。

/*  1*/ class MethodTest1 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int x = 100;
/*  4*/         printNumber(x);
/*  5*/     }
/*  6*/     static void printNumber (int n) {
/*  7*/         System.out.println(n);
/*  8*/     }
/*  9*/ }
asiaa1:~/comp3b b08a001$ java MethodTest1
100
asiaa1:~/comp3b b08a001$

メソッドを呼び出すとき、仮引数の変数 n が用意され、そこに実引数の値100が格納されます。

+-----+     +-----+
|     |copy |     |
| 100 |---->| 100 |
+-----+     +-----+
   x           n

次の例は、逆に、メソッドのデータ n = 200 を呼び出し側の変数 x に渡すつもりです。 この場合は失敗します。

/*  1*/ class MethodTest2 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int x = 100;
/*  4*/         setNumber(x);
/*  5*/         System.out.println(x);
/*  6*/     }
/*  7*/     static void setNumber (int n) {
/*  8*/         n = 200;
/*  9*/     }
/* 10*/ }
asiaa1:~/comp3b b08a001$ java MethodTest2
100
asiaa1:~/comp3b b08a001$

メソッドを実行すると、仮引数の変数 n には200が格納されますが、呼び出し側の変数 x は100のままなのです。

+-----+     +-----+
|     |     |     |
| 100 |     | 200 |
+-----+     +-----+
   x           n

基本データ型の場合、メソッドを呼び出しても、呼び出し側の実引数は変わらないことが分かります。 メソッドから呼び出し側にデータを渡すには、値を返すメソッドを定義して、return文でデータを渡せばよいのです。

6.1.3 インスタンスの共有

今日も、時刻を表すクラスTimeを利用します。

Time.java
/*  1*/ class Time {
/*  2*/     int hour, minute;
/*  3*/ }

次のプログラムにおいて、7行目の代入で変数 xy は同じインスタンスを参照します。 この状態を、 xy はインスタンスを 共有share ) しているといいます。 インスタンスを共有している場合、一方からインスタンスを変更すると、他方からも変更されたことになります。 9行目で、インスタンス xminute フィールドを変更しますと、インスタンス y も変更されるのです。

/*  1*/ class TimeTest4 {
/*  2*/     public static void main (String[] args) {
/*  3*/         Time x, y;
/*  4*/         x = new Time();
/*  5*/         x.hour = 13;
/*  6*/         x.minute = 15;
/*  7*/         y = x;
/*  8*/         System.out.println(y.hour + ":" + y.minute);
/*  9*/         x.minute = 45;
/* 10*/         System.out.println(y.hour + ":" + y.minute);
/* 11*/     }
/* 12*/ }
asiaa1:~/comp3b b08a001$ java TimeTest4
13:15
13:45
asiaa1:~/comp3b b08a001$
インスタンスの共有
図 6.1  インスタンスの共有

6.1.4 参照呼出し

次のプログラムでは、時刻の分をセットするメソッドsetMinuteを定義しています。 引数がインスタンスであることに注意してください。

5行目と6行目で、インスタンス x が14時45分を表すようにします。 8行目で、 xminute フィールドの値が30に変更されます。 従って、 x は14時30分を表すようになります。

/*  1*/ class TimeTest5 {
/*  2*/     public static void main (String[] args) {
/*  3*/         Time x;
/*  4*/         x = new Time();
/*  5*/         x.hour = 14;
/*  6*/         x.minute = 45;
/*  7*/         System.out.println(x.hour + ":" + x.minute);
/*  8*/         setMinute(x, 30);
/*  9*/         System.out.println(x.hour + ":" + x.minute);
/* 10*/     }
/* 11*/     static void setMinute (Time t, int minute) {
/* 12*/         t.minute = minute;
/* 13*/     }
/* 14*/ }
asiaa1:~/comp3b b08a001$ java TimeTest5
14:45
14:30
asiaa1:~/comp3b b08a001$

さて、メソッドを呼び出すと、仮引数には実引数の値のコピーが格納されることを思い出してください。 従って、メソッドの中でコピーである仮引数を変更しても、コピー元である実引数には影響を与えないはずです。 それにもかかわらず、このプログラムでは、8行目で実引数 x が変更されたように見えます。 この現象を理解するためには、参照について意識する必要があります。

8行目で、メソッド setMinute を呼び出すと、仮引数 t に実引数 x の値のコピーが格納されます。 x に格納されているデータは参照なので、 t に格納されるのは参照のコピーです。 従って、 xt は同じインスタンスを共有します。 そして、メソッドが実行され、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。

インスタンス引数のメソッド呼出し
図 6.2  インスタンス引数のメソッド呼出し

仮引数に実引数の値のコピーを格納する呼び出し方を、値呼び出しと言いました。 一方、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出しcall by reference ) と言います。 Javaのメソッド呼出しは値呼出しです。 しかし、引数がインスタンスですと、仮引数に参照のコピーが格納されますので、実質的に参照呼出しになるわけです。

以上の議論により、インスタンスのフィールドを読み書きすることは、メソッドにまとめられることが分かりました。 次のプログラムでは、インスタンスのフィールドを読み書きするメソッドを定義しています。 getHour は、 Time クラスのインスタンスを引数にとり、その hour フィールドの値を返します。 getMinute は、同様に minute フィールドの値を返します。 setHour は、 Time クラスのインスタンスと整数( int 型)を引数にとり、値を返さないメソッドです。 そのインスタンスの hour フィールドに、その整数を代入します。 setMinute は、同じく minute フィールドに代入します。

/*  1*/ class TimeTest6 {
/*  2*/     public static void main (String[] args) {
/*  3*/         Time x = new Time();
/*  4*/         setHour(x, 17);
/*  5*/         setMinute(x, 55);
/*  6*/         System.out.println(getHour(x) + ":" + getMinute(x));
/*  7*/     }
/*  8*/     static int getHour (Time t) {
/*  9*/         return t.hour;
/* 10*/     }
/* 11*/     static int getMinute (Time t) {
/* 12*/         return t.minute;
/* 13*/     }
/* 14*/     static void setHour (Time t, int hour) {
/* 15*/         t.hour = hour;
/* 16*/     }
/* 17*/     static void setMinute (Time t, int minute) {
/* 18*/         t.minute = minute;
/* 19*/     }
/* 20*/ }
asiaa1:~/comp3b b08a001$ java TimeTest6
17:55
asiaa1:~/comp3b b08a001$

6.1.5 参照とメソッドの例

参照とメソッドの例として、次の問題を考えます。

ある人が、ある位置から、北へ1km, 東へ1km, 東へ1km, 北へ1km, 西へ1km移動したとします。 結局、この人は、元の位置から北へ何km, 東へ何km移動したでしょうか。

この問題を、インスタンスとメソッドを用いて解決します。

ファイルPosition.javaでは、位置を格納するために、クラス Position を定義します。 フィールド north には北への移動距離を、フィールド east には東への移動距離を格納します。 コンストラクタも定義します。

Position.java
/*  1*/ class Position {
/*  2*/     int north, east;
/*  3*/     Position (int north, int east) {
/*  4*/         this.north = north;
/*  5*/         this.east = east;
/*  6*/     }
/*  7*/ }

メソッドを用いない場合、プログラムは次のようになります。

PositionMain.java
/*  1*/ class PositionMain {
/*  2*/     public static void main (String[] args) {
/*  3*/         Position position = new Position(0, 0);
/*  4*/         position.north++;
/*  5*/         position.east++;
/*  6*/         position.east++;
/*  7*/         position.north++;
/*  8*/         position.east--;
/*  9*/         System.out.println("北に" + position.north + "km");
/* 10*/         System.out.println("東に" + position.east + "km");
/* 11*/     }
/* 12*/ }
asiaa1:~/comp3b b08a001$ java PositionMain
北に2km
東に1km
asiaa1:~/comp3b b08a001$

このプログラムの欠点は、西へ移動することを position.east-- と表していることです。 メソッドを利用すれば、文 position.east-- に、西へ移動するという名前が付けられます。

メソッドを用いた場合は、次のようになります。

移動のためのメソッドは、 moveNorth , moveEast , moveSouth , moveWest を定義します。 これらのメソッドは、それぞれ、北、東、南、西へ移動したことを表すものです。 引数は、クラス Positionp です。 これらのメソッドは値を返さないので、 static void と指定します。

画面出力のためのメソッドは、 printPosition を定義します。 引数は、クラス Positionp です。 このメソッドは値を返さないので、 static void と指定します。

PositionMain2.java
/*  1*/ class PositionMain2 {
/*  2*/     public static void main (String[] args) {
/*  3*/         Position position = new Position(0, 0);
/*  4*/         moveNorth(position);
/*  5*/         moveEast(position);
/*  6*/         moveEast(position);
/*  7*/         moveNorth(position);
/*  8*/         moveWest(position);
/*  9*/         printPosition(position);
/* 10*/     }
/* 11*/     static void moveNorth (Position p) {
/* 12*/         p.north++;
/* 13*/     }
/* 14*/     static void moveEast (Position p) {
/* 15*/         p.east++;
/* 16*/     }
/* 17*/     static void moveSouth (Position p) {
/* 18*/         p.north--;
/* 19*/     }
/* 20*/     static void moveWest (Position p) {
/* 21*/         p.east--;
/* 22*/     }
/* 23*/     static void printPosition (Position p) {
/* 24*/         System.out.println("北に" + p.north + "km");
/* 25*/         System.out.println("東に" + p.east + "km");
/* 26*/     }
/* 27*/ }

6.2 演習6

ある年のある村役場では、出生届があり、死亡届があり、死亡届があり、出生届があり、出生届があって、結局、出生届3通、死亡届2通だったとします。 この人口の変化を計算するプログラムを作成してください。

ファイルPopulation.javaでは、出生届と死亡届の枚数を格納するために、クラス Population を定義します。 フィールド birth には出生届の枚数を、 death には死亡届の枚数を格納します。 コンストラクタは、引数が birth , death の順になるように定義します。

ファイルPopulationMain.javaでは、メソッド receiveBirth を定義します。 これは、出生届を一通受け取るものです。 引数はクラス Populationp です。 このメソッドは値を返さないので、 static void と指定します。

ファイルPopulationMain.javaでは、メソッド receiveDeath を定義します。 これは、死亡届を一通受け取るものです。 引数はクラス Populationp です。 このメソッドは値を返さないので、 static void と指定します。

ファイルPopulationMain.javaでは、メソッド printPopulation を定義します。 これは、出生届と死亡届の枚数を画面出力するものです。 引数はクラス Populationp です。 このメソッドは値を返さないので、 static void と指定します。

そして、ファイルPopulationMain.javaでは、次のようにメソッド receiveBirthreceiveDeath を呼び出して、人口の変化を計算します。

Population population = new Population(0, 0);
receiveBirth(population);
receiveDeath(population);
receiveDeath(population);
receiveBirth(population);
receiveBirth(population);
printPopulation(population);
asiaa1:~/comp3b b08a001$ java PopulationMain
出生届3通
死亡届2通
asiaa1:~/comp3b b08a001$

6.3 レポート課題

今日の演習6の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b08a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(11月7日)を明記してください。


6.4 参考文献


[小西ホームページ]   [目次・索引]   [前の授業]   [次の授業]

2008年11月7日更新
小西 善二郎 <konishi@cis.twcu.ac.jp>
Copyright (C) 2008 Zenjiro Konishi. All rights reserved.