目次 | 索引 |
---|---|
前回の授業では、次のようなプログラムを動かしました。
/* 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*/ }
b00a001@Ampere:~/java% java TimeTest 9:30 b00a001@Ampere:~/java%
このプログラムで、変数
x
には何が格納されるでしょうか。
変数にはデータが一つしか格納できませんので、インスタンスそのものは入りません。
実は、
x
には参照とよばれるデータが格納されます。
参照
(
reference
)
とは、メモリの中でデータが配置されている場所のことです。
変数
x
には、インスタンスが置かれている「場所データ」が格納されるのです。
参照をポインタやアドレスのようなものだと思っても差し支えありません。
参照は、しばしば下図のような矢印で書き表わされます。
上記のプログラムは、変数を宣言し、インスタンスを生成してその変数に代入し、そしてそのフィールドに数を代入しています。 この処理の流れを順に表現したのが以下の図です。
インスタンスと同様、変数に配列を代入しましても、その変数にはその配列への参照が格納されます。 例えば、
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*/ }
b00a001@Ampere:~/java% java TimeArray 13:15 14:55 16:35 b00a001@Ampere:~/java%
変数にインスタンスの配列を代入しますと、配列への参照とインスタンスへの参照が組み合わさった参照構造が得られます。 上記の場合ですと、以下のようになります。
この参照構造ができあがるまでの過程は次の通りです。 インスタンスの配列が利用できるようになるまでには、多くのステップが必要であることを理解してください。
以下では、主にインスタンスへの参照について説明します。 配列への参照についても同じことが言えます。
参照が格納される変数の値を変えることは、参照先を変更することになります。
次のプログラムでは、まず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*/ }
b00a001@Ampere:~/java% java TimeTest2 10:45 11:15 b00a001@Ampere:~/java%
参照が格納される変数には、新しく生成されるインスタンスだけではなく、すでに生成されたインスタンスも代入できます。 この場合、その変数にはそのインスタンスへの参照のコピーが格納されます。 インスタンスそのものはコピーされません。
また、整数(
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*/ }
b00a001@Ampere:~/java% java TimeTest3 12:30 12:30 12:30 x is equal to y x is not equal to z b00a001@Ampere:~/java%
次のプログラムにおいて、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*/ }
b00a001@Ampere:~/java% java TimeTest4 13:15 13:45 b00a001@Ampere:~/java%
これまで定義したメソッドは、引数や返り値が整数(
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*/ }
b00a001@Ampere:~/java% java TimeTest5 14:45 14:30 b00a001@Ampere:~/java%
さて、メソッドを呼び出しますと、仮引数には実引数の値のコピーが格納されることを思い出してください。
従って、メソッドの中でコピーである仮引数を変更しても、コピー元である実引数には影響を与えないはずです。
それにもかかわらず、このプログラムでは、7行目で実引数
x
が変更されたように見えます。
この現象を理解するためには、参照について意識する必要があります。
7行目で、メソッド
setMinute
を呼び出しますと、仮引数
t
に実引数
x
の値のコピーが格納されます。
x
に格納されているデータは参照ですので、
t
に格納されるのは参照のコピーです。
従って、
x
と
t
は同じインスタンスを共有します。
そして、メソッドが実行され、この共有しているインスタンスは変更されます。
このようにして、インスタンス
x
は、14時45分から14時30分に変更されるのです。
仮引数に実引数の値のコピーを格納する呼び出し方を、 値呼出し ( call by value ) とよびます。 また、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出し ( call by reference ) とよびます。 Javaのメソッド呼出しは値呼出しです。 しかし、引数がインスタンスですと、仮引数に参照のコピーが格納されますので、実質的に参照呼出しになるわけです。
参照の例として、電車の発車時刻を変更する計算を考えます。 仮に、今日は事故の影響で、通常より25分遅れて運転されているものとします。 以下の通常の発車時刻をインスタンスの配列に格納し、メソッド呼出しによって25分遅れに変更します。
通常 | 今日 |
---|---|
8:30 | 8:55 |
8:40 | 9:05 |
8:50 | 9:15 |
9:00 | 9:25 |
9:10 | 9:35 |
ファイル Departure.java では、発車時刻を格納するために、
Departure
というクラスを定義します。
フィールド
hour
に時を、フィールド
minute
に分を格納します。
コンストラクタも定義します。
/* 1*/ class Departure { /* 2*/ int hour, minute; /* 3*/ Departure (int hour, int minute) { /* 4*/ this.hour = hour; /* 5*/ this.minute = minute; /* 6*/ } /* 7*/ }
ファイル DepartureMain.java では、メソッド show と delay を定義します。
メソッド
show
は、例えば9時ちょうどが 9:0 とならないように、発車時刻を出力するものです。
引数は、
Departure
の
d
のみです。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、
d
.
minute
が10未満ならば"0"を追加することです。
メソッド
delay
は、発車時刻を遅らせるものです。
引数は、
Departure
の
d
と、遅れ
minute
です。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、単に
d
.
minute
+
minute
を計算したのでは、60以上になるかもしれませんので、60で割ってそれだけ
d
.
hour
を増加させ、割った余りを
d
.
minute
に格納することです。
アルゴリズムは次のようになります。
Departure
の配列の変数
deps
を宣言し、上記の表の通り初期化する。
プログラムは以下のようになります。
/* 1*/ class DepartureMain { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ int late = 25; /* 5*/ Departure[] deps = {new Departure(8, 30), /* 6*/ new Departure(8, 40), /* 7*/ new Departure(8, 50), /* 8*/ new Departure(9, 0), /* 9*/ new Departure(9, 10)}; /*10*/ System.out.println("Usual\tToday"); /*11*/ for (i = 0; i < deps.length; i++) { /*12*/ show(deps[i]); /*13*/ System.out.print("\t"); /*14*/ delay(deps[i], late); /*15*/ show(deps[i]); /*16*/ System.out.println(); /*17*/ } /*18*/ } /*19*/ static void show (Departure d) { /*20*/ System.out.print(d.hour + ":"); /*21*/ if (d.minute < 10) { /*22*/ System.out.print("0"); /*23*/ } /*24*/ System.out.print(d.minute); /*25*/ } /*26*/ static void delay (Departure d, int minute) { /*27*/ d.hour = d.hour + (d.minute + minute) / 60; /*28*/ d.minute = (d.minute + minute) % 60; /*29*/ } /*30*/ }
b00a001@Ampere:~/java% java DepartureMain Usual Today 8:30 8:55 8:40 9:05 8:50 9:15 9:00 9:25 9:10 9:35 b00a001@Ampere:~/java%
以下のように、数人の生年月日と2002年4月1日現在の年齢が、インスタンスの配列に格納されているとします。 メソッド呼出しによって、年齢の部分を今日(2002/11/28)現在の年齢に変更するプログラムを作成してください。
生年月日 | 2002/4/1現在 | 2002/11/28現在 |
---|---|---|
1981/11/7 | 20 | 21 |
1981/11/28 | 20 | 21 |
1981/11/30 | 20 | 20 |
1982/1/31 | 20 | 20 |
1982/4/1 | 20 | 20 |
ファイル Person.java では、生年月日と年齢を格納するために、
Person
というクラスを定義します。
year
というフィールドには生まれ年を、
month
というフィールドには生まれ月を、
day
というフィールドには生まれ日を、
age
というフィールドには年齢を、それぞれ格納します。
コンストラクタは、このフィールドの順に定義します。
ファイル PersonMain.java では、次のようにコンストラクタを呼び出して、配列 pers にすべての生年月日と年齢を格納します。 (コピー・アンド・ペーストして使ってください。)
Person[] pers = {new Person(1981, 11, 7, 20), new Person(1981, 11, 28, 20), new Person(1981, 11, 30, 20), new Person(1982, 1, 31, 20), new Person(1982, 4, 1, 20)};
年齢を計算し直すメソッドの名前は
setAge
とします。
引数は、
Person
の
p
, 今日の年の
year
, 今日の月の
month
, および今日の日の
day
です。
このメソッドは値を返しません。
メソッド定義の要点は、いったん
year
−
p
.
year
を
p
.
age
に格納し、もしまだ誕生日をむかえていなかったら、そこから 1 減らすことです。
アルゴリズムは次のようにします。
Person
の配列の変数
pers
を宣言し、上記の表の通り初期化する。
b00a001@Ampere:~/java% java PersonMain Birthdate Age 1981/11/7 21 1981/11/28 21 1981/11/30 20 1982/1/31 20 1982/4/1 20 b00a001@Ampere:~/java%
今日の演習10に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(11/28)を明記してください。