前回の授業では、次のようなプログラムを動かしました。
/* 1*/ class Time { /* 2*/ int hour, minute; /* 3*/ }
/* 1*/ class TimeTest { /* 2*/ public static void main (String[] args) { /* 3*/ Time x; /* 4*/ x = new Time(); /* 5*/ x.hour = 9; /* 6*/ x.minute = 30; /* 7*/ System.out.println(x.hour + ":" + x.minute); /* 8*/ } /* 9*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest 9:30 b04a001@AsiaA1:~/comp2b%
このプログラムで、変数 x には何が格納されるでしょうか。 変数にはデータが一つしか格納できませんので、インスタンスそのものは入りません。 実は、 x には参照とよばれるデータが格納されます。
参照 ( reference ) とは、メモリの中でデータが配置されている場所のことです。 変数 x には、インスタンスが置かれている「場所データ」が格納されるのです。 参照をポインタやアドレスのようなものだと思っても差し支えありません。 参照を、WWWにおけるURLのようなものだと考えてもよいでしょう。 参照は、しばしば下図のような矢印で書き表わされます。
上記のプログラムは、変数を宣言し、インスタンスを生成してその変数に代入し、そしてそのフィールドに数を代入しています。 この処理の流れを順に表現したのが以下の図です。
インスタンスと同様、変数に配列を代入しましても、その変数にはその配列への参照が格納されます。 例えば、
int[] a = {91, 74, 57, 40, 23};
のように配列を初期化しますと、次の図のようになります。
前回の授業では、以下のようなプログラムも紹介しました。 これがインスタンスの配列を扱っていることを思い出してください。
/* 1*/ class TimeArray { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ Time[] a = new Time[3]; /* 5*/ for (i = 0; i < a.length; i++) { /* 6*/ a[i] = new Time(); /* 7*/ } /* 8*/ a[0].hour = 13; a[0].minute = 15; /* 9*/ a[1].hour = 14; a[1].minute = 55; /* 10*/ a[2].hour = 16; a[2].minute = 35; /* 11*/ for (i = 0; i < a.length; i++) { /* 12*/ System.out.println(a[i].hour + ":" + a[i].minute); /* 13*/ } /* 14*/ } /* 15*/ }
b04a001@AsiaA1:~/comp2b% java TimeArray 13:15 14:55 16:35 b04a001@AsiaA1:~/comp2b%
変数にインスタンスの配列を代入しますと、配列への参照とインスタンスへの参照が組み合わさった参照構造が得られます。 上記の場合ですと、以下のようになります。
この参照構造ができあがるまでの過程は次の通りです。 インスタンスの配列が利用できるようになるまでには、多くのステップが必要であることを理解してください。
以下では、主にインスタンスへの参照について説明します。 配列への参照についても同じことが言えます。
参照が格納される変数の値を変えることは、参照先を変更することになります。
次のプログラムでは、まず10時45分を表すインスタンスへの参照が変数 x に格納されます。 そして x の値は、11時15分を表すインスタンスへの参照に置き換わります。
/* 1*/ class TimeTest2 { /* 2*/ public static void main (String[] args) { /* 3*/ Time x; /* 4*/ x = new Time(); /* 5*/ x.hour = 10; /* 6*/ x.minute = 45; /* 7*/ System.out.println(x.hour + ":" + x.minute); /* 8*/ x = new Time(); /* 9*/ x.hour = 11; /* 10*/ x.minute = 15; /* 11*/ System.out.println(x.hour + ":" + x.minute); /* 12*/ } /* 13*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest2 10:45 11:15 b04a001@AsiaA1:~/comp2b%
参照が格納される変数には、新しく生成されるインスタンスだけではなく、すでに生成されたインスタンスも代入できます。 この場合、その変数にはそのインスタンスへの参照のコピーが格納されます。 インスタンスそのものはコピーされません。
また、整数(
int
型)どうしでは、関係演算子として
>
や
==
などが使えましたが、インスタンスどうしでは
==
が使えます。
これは参照先が一致していることを意味します。
フィールドの値がすべて等しくても、置かれている場所が異なれば、インスタンスは(
==
の意味で)等しくありません。
次のプログラムは、まず変数 x に12時30分を表すインスタンスへの参照を格納します。 そして、変数 y にはそのインスタンスを直接代入し、変数 z にはフィールドごとに代入します。 x と y は等しいインスタンスだと判定されますが、 x と z は等しくないと判定されます。
/* 1*/ class TimeTest3 { /* 2*/ public static void main (String[] args) { /* 3*/ Time x, y, z; /* 4*/ x = new Time(); /* 5*/ x.hour = 12; /* 6*/ x.minute = 30; /* 7*/ System.out.println(x.hour + ":" + x.minute); /* 8*/ y = x; /* 9*/ System.out.println(y.hour + ":" + y.minute); /* 10*/ z = new Time(); /* 11*/ z.hour = x.hour; /* 12*/ z.minute = x.minute; /* 13*/ System.out.println(z.hour + ":" + z.minute); /* 14*/ if (x == y) { /* 15*/ System.out.println("x is equal to y"); /* 16*/ } else { /* 17*/ System.out.println("x is not equal to y"); /* 18*/ } /* 19*/ if (x == z) { /* 20*/ System.out.println("x is equal to z"); /* 21*/ } else { /* 22*/ System.out.println("x is not equal to z"); /* 23*/ } /* 24*/ } /* 25*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest3 12:30 12:30 12:30 x is equal to y x is not equal to z b04a001@AsiaA1:~/comp2b%
次のプログラムにおいて、7行目の代入で変数 x と y は同じインスタンスを参照します。 この状態を、 x と y はインスタンスを 共有 ( share ) しているといいます。 インスタンスを共有している場合、一方からインスタンスを変更しますと、他方からも変更されたことになります。 9行目で、インスタンス x の minute フィールドを変更しますと、インスタンス y も変更されるのです。
/* 1*/ class TimeTest4 { /* 2*/ public static void main (String[] args) { /* 3*/ Time x = new Time(); /* 4*/ Time y; /* 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*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest4 13:15 13:45 b04a001@AsiaA1:~/comp2b%
これまで定義したメソッドは、引数や返り値が整数(
int
型)のものだけでした。
(引数や返り値がないものもありました。)
これまで
int
と書いていた部分にクラス名を書けば、引数や返り値がインスタンスであるメソッドが定義できます。
例えば、引数が
Time
クラスのインスタンスで、返り値が整数(
int
型)である(クラス)メソッドは、
static int methodname (Time argument) { ... }
と定義します。
また、引数が
Time
クラスのインスタンスと整数(
int
型)で、値を返さない(クラス)メソッドは、
static void methodname (Time argument1, int argument2) { ... }
と定義します。
次のプログラムでは、引数がインスタンスであるメソッドをいくつか定義しています。
getHour
は、
Time
クラスのインスタンスを引数にとり、その
hour
フィールドの値を返します。
getMinute
は、同様に
minute
フィールドの値を返します。
setHour
は、
Time
クラスのインスタンスと整数(
int
型)を引数にとり、値を返さないメソッドです。
そのインスタンスの
hour
フィールドに、その整数を代入します。
setMinute
は、同じく
minute
フィールドに代入します。
4行目と5行目で、インスタンス x が14時45分を表すようにします。 7行目で、 x の minute フィールドの値が30に変更されます。 従って、 x は14時30分を表すようになります。
/* 1*/ class TimeTest5 { /* 2*/ public static void main (String[] args) { /* 3*/ Time x = new Time(); /* 4*/ setHour(x, 14); /* 5*/ setMinute(x, 45); /* 6*/ System.out.println(getHour(x) + ":" + getMinute(x)); /* 7*/ setMinute(x, 30); /* 8*/ System.out.println(getHour(x) + ":" + getMinute(x)); /* 9*/ } /* 10*/ static int getHour (Time t) { /* 11*/ return t.hour; /* 12*/ } /* 13*/ static int getMinute (Time t) { /* 14*/ return t.minute; /* 15*/ } /* 16*/ static void setHour (Time t, int hour) { /* 17*/ t.hour = hour; /* 18*/ } /* 19*/ static void setMinute (Time t, int minute) { /* 20*/ t.minute = minute; /* 21*/ } /* 22*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest5 14:45 14:30 b04a001@AsiaA1:~/comp2b%
さて、メソッドを呼び出しますと、仮引数には実引数の値のコピーが格納されることを思い出してください。 従って、メソッドの中でコピーである仮引数を変更しても、コピー元である実引数には影響を与えないはずです。 それにもかかわらず、このプログラムでは、7行目で実引数 x が変更されたように見えます。 この現象を理解するためには、参照について意識する必要があります。
7行目で、メソッド setMinute を呼び出しますと、仮引数 t に実引数 x の値のコピーが格納されます。 x に格納されているデータは参照ですので、 t に格納されるのは参照のコピーです。 従って、 x と t は同じインスタンスを共有します。 そして、メソッドが実行され、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。
仮引数に実引数の値のコピーを格納する呼び出し方を、 値呼出し ( call by value ) とよびます。 また、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出し ( call by reference ) とよびます。 Javaのメソッド呼出しは値呼出しです。 しかし、引数がインスタンスですと、仮引数に参照のコピーが格納されますので、実質的に参照呼出しになるわけです。
参照の例として、次の問題を考えます。
ある人が、ある位置から、北へ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 to the north"); /* 10*/ System.out.println(position.east + "km to the east"); /* 11*/ } /* 12*/ }
b04a001@AsiaA1:~/comp2b% java PositionMain 2km to the north 1km to the east b04a001@AsiaA1:~/comp2b%
このプログラムの欠点は、西へ移動することを
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 to the north"); /* 25*/ System.out.println(p.east + "km to the east"); /* 26*/ } /* 27*/ }
あるスポーツの試合で、味方が得点し、敵が得点し、敵が得点し、味方が得点し、味方が得点して、結局3-2で味方が勝ったとします。 この得点の変化を計算するプログラムを作成してください。
ファイルMatch.javaでは、何対何かを格納するために、クラス
Match
を定義します。
フィールド
friend
には味方の得点を、
enemy
には敵の得点を格納します。
コンストラクタは、引数が
friend
,
enemy
の順になるように定義します。
ファイルMatchMain.javaでは、メソッド
friendScore
を定義します。
これは、味方の得点を一つ増加させるものです。
引数はクラス
Match
の
m
です。
このメソッドは値を返しませんので、
static void
と指定します。
ファイルMatchMain.javaでは、メソッド
enemyScore
を定義します。
これは、敵の得点を一つ増加させるものです。
引数はクラス
Match
の
m
です。
このメソッドは値を返しませんので、
static void
と指定します。
ファイルMatchMain.javaでは、メソッド
printMatch
を定義します。
これは、何対何かを画面出力するものです。
引数はクラス
Match
の
m
です。
このメソッドは値を返しませんので、
static void
と指定します。
そして、ファイルMatchMain.javaでは、次のようにメソッド friendScore と enemyScore を呼び出して、得点の変化を計算します。
Match match = new Match(0, 0); friendScore(match); enemyScore(match); enemyScore(match); friendScore(match); friendScore(match); printMatch(match);
b04a001@AsiaA1:~/comp2b% java MatchMain 3-2 b04a001@AsiaA1:~/comp2b%
余力のある人は、メソッド friendScore と enemyScore の定義に画面出力を追加して、どちらが得点して何対何になったかが分かるようにしてください。
b04a001@AsiaA1:~/comp2b% java MatchMain The friends make a score! 1-0 The enemies make a score! 1-1 The enemies make a score! 1-2 The friends make a score! 2-2 The friends make a score! 3-2 3-2 b04a001@AsiaA1:~/comp2b%
今日の演習9の答案(Javaプログラム)を、メールでkonishi@twcu.ac.jp宛に提出してください。 メールを送るときは、大学のパソコンを使うか、大学のメール・サーバに接続するかして、差出人が大学のメール・アドレスになるようにしてください。 メールの本文には、学生番号、氏名、科目名、授業日(6月23日)を明記してください。