関数 ( function ) とは、呼び出されるとまとまった計算をして、呼び出し側に計算結果を返すプログラム単位です。 手続き ( procedure ) とは、呼び出されるとまとまった処理をして、呼び出し側に戻るプログラム単位です。 Javaでは、関数も手続きも メソッド ( method ) で実現します。
メソッドには、インスタンス・メソッドとクラス・メソッドがあります。 今日は、(同じクラスの)クラス・メソッドのみを扱います。
メソッドは、定義してから呼び出します。 メソッド定義の形式は、関数の場合は
static クラス名 メソッド名 (クラス名 変数名, ...) { 文の並び }
となり、手続きの場合は
static void メソッド名 (クラス名 変数名, ...) { 文の並び }
となります。 メソッド呼出しの形式は、関数の場合は式として
メソッド名(式, ...)
となり、手続きの場合は文として
メソッド名(式, ...);
となります。
メソッドの括弧の中は、 引数 ( 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文でデータを渡せばよいのです。
今日も、時刻を表すクラスTimeを利用します。
/* 1*/ class Time { /* 2*/ int hour, minute; /* 3*/ }
次のプログラムにおいて、7行目の代入で変数 x と y は同じインスタンスを参照します。 この状態を、 x と y はインスタンスを 共有 ( share ) しているといいます。 インスタンスを共有している場合、一方からインスタンスを変更すると、他方からも変更されたことになります。 9行目で、インスタンス x の minute フィールドを変更しますと、インスタンス 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$
次のプログラムでは、時刻の分をセットするメソッドsetMinuteを定義しています。 引数がインスタンスであることに注意してください。
5行目と6行目で、インスタンス x が14時45分を表すようにします。 8行目で、 x の minute フィールドの値が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 に格納されるのは参照のコピーです。 従って、 x と t は同じインスタンスを共有します。 そして、メソッドが実行され、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。
仮引数に実引数の値のコピーを格納する呼び出し方を、値呼び出しと言いました。 一方、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出し ( 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$
参照とメソッドの例として、次の問題を考えます。
ある人が、ある位置から、北へ1km, 東へ1km, 東へ1km, 北へ1km, 西へ1km移動したとします。 結局、この人は、元の位置から北へ何km, 東へ何km移動したでしょうか。
この問題を、インスタンスとメソッドを用いて解決します。
ファイルPosition.javaでは、位置を格納するために、クラス
Position
を定義します。
フィールド
north
には北への移動距離を、フィールド
east
には東への移動距離を格納します。
コンストラクタも定義します。
/* 1*/ class Position { /* 2*/ int north, east; /* 3*/ Position (int north, int east) { /* 4*/ this.north = north; /* 5*/ this.east = east; /* 6*/ } /* 7*/ }
メソッドを用いない場合、プログラムは次のようになります。
/* 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
を定義します。
これらのメソッドは、それぞれ、北、東、南、西へ移動したことを表すものです。
引数は、クラス
Position
の
p
です。
これらのメソッドは値を返さないので、
static void
と指定します。
画面出力のためのメソッドは、
printPosition
を定義します。
引数は、クラス
Position
の
p
です。
このメソッドは値を返さないので、
static void
と指定します。
/* 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*/ }
ある年のある村役場では、出生届があり、死亡届があり、死亡届があり、出生届があり、出生届があって、結局、出生届3通、死亡届2通だったとします。 この人口の変化を計算するプログラムを作成してください。
ファイルPopulation.javaでは、出生届と死亡届の枚数を格納するために、クラス
Population
を定義します。
フィールド
birth
には出生届の枚数を、
death
には死亡届の枚数を格納します。
コンストラクタは、引数が
birth
,
death
の順になるように定義します。
ファイルPopulationMain.javaでは、メソッド
receiveBirth
を定義します。
これは、出生届を一通受け取るものです。
引数はクラス
Population
の
p
です。
このメソッドは値を返さないので、
static void
と指定します。
ファイルPopulationMain.javaでは、メソッド
receiveDeath
を定義します。
これは、死亡届を一通受け取るものです。
引数はクラス
Population
の
p
です。
このメソッドは値を返さないので、
static void
と指定します。
ファイルPopulationMain.javaでは、メソッド
printPopulation
を定義します。
これは、出生届と死亡届の枚数を画面出力するものです。
引数はクラス
Population
の
p
です。
このメソッドは値を返さないので、
static void
と指定します。
そして、ファイルPopulationMain.javaでは、次のようにメソッド receiveBirth と receiveDeath を呼び出して、人口の変化を計算します。
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の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b08a001@cis.twcu.ac.jpなど)とし、宛先はkonishi@cis.twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(11月7日)を明記してください。