目次 | 索引 |
---|---|
前回の授業では、次のようなプログラムを動かしました。
/* 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@AsiaA1:~/java% java TimeTest 9:30 b00a001@AsiaA1:~/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@AsiaA1:~/java% java TimeArray 13:15 14:55 16:35 b00a001@AsiaA1:~/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@AsiaA1:~/java% java TimeTest2 10:45 11:15 b00a001@AsiaA1:~/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@AsiaA1:~/java% java TimeTest3 12:30 12:30 12:30 x is equal to y x is not equal to z b00a001@AsiaA1:~/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@AsiaA1:~/java% java TimeTest4 13:15 13:45 b00a001@AsiaA1:~/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@AsiaA1:~/java% java TimeTest5 14:45 14:30 b00a001@AsiaA1:~/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
を定義します。
このメソッドは、例えば9時ちょうどが 9:0 とならないように、発車時刻を出力するものです。
引数は、クラス
Departure
の
d
のみです。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、フィールド
d
.
minute
の値が10未満ならば、文字列"0"を追加することです。
ファイル DepartureMain.java では、メソッド
delay
を定義します。
このメソッドは、発車時刻を遅らせるものです。
引数は、クラス
Departure
の
d
と、遅れを表す
minute
です。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、単に
d
.
minute
+
minute
を計算したのでは、60以上になるかもしれませんので、60で割ってそれだけフィールド
d
.
hour
を増加させ、割った余りをフィールド
d
.
minute
に格納することです。
アルゴリズムは次のようになります。
Departure
の配列
deps
を宣言し、上記の表の通り初期化する。
プログラムは以下のようになります。
/* 1*/ // 円記号対策のコメント /* 2*/ class DepartureMain { /* 3*/ public static void main (String[] args) { /* 4*/ int i, 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@AsiaA1:~/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@AsiaA1:~/java%
今月A子は以下のように銀行口座のお金を出し入れしました。 思い出しますと、今月への繰り越し金は20万円でした。 通帳に記帳したときの預け入れ、引き出し、および残高の一覧を作成します。
No. | 預け入れ | 引き出し |
---|---|---|
0 | 8万円 | |
1 | 3万円 | |
2 | 5万円 | |
3 | 1万円 | |
4 | 2万円 |
ファイルBanking.javaでは、お金の出し入れを格納するために、クラス
Banking
を定義します。
フィールド
deposit
には預け入れ金額を、フィールド
withdrawal
には引き出し金額を、フィールド
balance
には残高を格納します。
コンストラクタは、引数が預け入れ金額、引き出し金額、残高の順になるように定義します。
ファイルBankingMain.javaでは、次のようにコンストラクタを呼び出して、配列 bankings にすべてのお金の出し入れを格納します。 残高をとりあえず 0 にしていることに注意してください。
Banking[] bankings = {new Banking(0, 80000, 0), new Banking(30000, 0, 0), new Banking(0, 50000, 0), new Banking(10000, 0, 0), new Banking(20000, 0, 0)};
ファイルBankingMain.javaでは、メソッド
settle
を定義します。
これは、残高を計算して格納するものです。
引数は、クラス
Banking
の
b
と直前の残高
balance
です。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、直前の残高
balance
プラス預け入れ金額
b
.
deposit
マイナス引き出し金額
b
.
withdrawal
の値をフィールド
b
.
balance
に格納することです。
ファイルBankingMain.javaでは、メソッド
show
を定義します。
これは、預け入れ金額、引き出し金額、および残高を出力するものです。
引数は、クラス
Banking
の
b
のみです。
このメソッドは値を返しませんので、
static void
と指定します。
メソッド定義の要点は、預け入れがあればその値を改行なしで出力し、タブ2つを改行なして出力し、引き出しがあればその値を改行なしで出力し、タブ2つを改行なしで出力し、残高を出力することです。
アルゴリズムは次のようにします。
Banking
の配列
bankings
を宣言し、上記の要領で初期化する。
このアルゴリズムをプログラムにしてください。
b00a001@AsiaA1:~/java% java BankingMain Deposits Withdrawals Balances 200000 80000 120000 30000 150000 50000 100000 10000 110000 20000 130000 b00a001@AsiaA1:~/java%
余力のある人は、同じ計算をするプログラムを、メソッドを用いないで作成してください。
今日の演習10に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(11/27)を明記してください。