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

情報処理IIIA(Javaプログラミング入門)第10回

目次 索引
10.1 参照
10.1.1 参照とは
10.1.2 参照と配列
10.1.3 参照の操作
10.1.4 インスタンスの共有
10.2 参照とメソッド
10.2.1 参照呼出し
10.2.2 参照の使用例
10.3 演習10
10.4 レポート課題
値呼出し  共有  参照  参照呼出し 

10.1 参照

10.1.1 参照とは

前回の授業では、次のようなプログラムを動かしました。

Time.java
/*  1*/ class Time {
/*  2*/     int hour, minute;
/*  3*/ }
TimeTest.java
/*  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 には、インスタンスが置かれている「場所データ」が格納されるのです。 参照をポインタやアドレスのようなものだと思っても差し支えありません。 参照は、しばしば下図のような矢印で書き表わされます。

An image of a reference
図 10.1  参照のイメージ

上記のプログラムは、変数を宣言し、インスタンスを生成してその変数に代入し、そしてそのフィールドに数を代入しています。 この処理の流れを順に表現したのが以下の図です。

An assignment of a reference
An assignment of a reference
An assignment of a reference
図 10.2  参照の格納

10.1.2 参照と配列

インスタンスと同様、変数に配列を代入しましても、その変数にはその配列への参照が格納されます。 例えば、

int[] a = {91, 74, 57, 40, 23};

のように配列を初期化しますと、次の図のようになります。

A reference to an array
図 10.3  配列への参照

前回の授業では、以下のようなプログラムも紹介しました。 これがインスタンスの配列を扱っていることを思い出してください。

TimeArray.java
/*  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%

変数にインスタンスの配列を代入しますと、配列への参照とインスタンスへの参照が組み合わさった参照構造が得られます。 上記の場合ですと、以下のようになります。

An array of instances
図 10.4  インスタンスの配列

この参照構造ができあがるまでの過程は次の通りです。 インスタンスの配列が利用できるようになるまでには、多くのステップが必要であることを理解してください。

Assignments of references
Assignments of references
Assignments of references
Assignments of references
図 10.5  配列における参照の格納

以下では、主にインスタンスへの参照について説明します。 配列への参照についても同じことが言えます。

10.1.3 参照の操作

参照が格納される変数の値を変えることは、参照先を変更することになります。

次のプログラムでは、まず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%
Changing references
図 10.6  参照の変更

参照が格納される変数には、新しく生成されるインスタンスだけではなく、すでに生成されたインスタンスも代入できます。 この場合、その変数にはそのインスタンスへの参照のコピーが格納されます。 インスタンスそのものはコピーされません。

また、整数( int 型)どうしでは、関係演算子として >== などが使えましたが、インスタンスどうしでは == が使えます。 これは参照先が一致していることを意味します。 フィールドの値がすべて等しくても、置かれている場所が異なれば、インスタンスは( == の意味で)等しくありません。

次のプログラムは、まず変数 x に12時30分を表すインスタンスへの参照を格納します。 そして、変数 y にはそのインスタンスを直接代入し、変数 z にはフィールドごとに代入します。 xy は等しいインスタンスだと判定されますが、 xz は等しくないと判定されます。

/*  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%
Equality of instances
図 10.7  インスタンスの比較

10.1.4 インスタンスの共有

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

10.2 参照とメソッド

10.2.1 参照呼出し

これまで定義したメソッドは、引数や返り値が整数( 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行目で、 xminute フィールドの値が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 に格納されるのは参照のコピーです。 従って、 xt は同じインスタンスを共有します。 そして、メソッドが実行され、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。

Method call with instance arguments
図 10.9  インスタンス引数のメソッド呼出し

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

10.2.2 参照の使用例

参照の例として、電車の発車時刻を変更する計算を考えます。 仮に、今日は事故の影響で、通常より25分遅れて運転されているものとします。 以下の通常の発車時刻をインスタンスの配列に格納し、メソッド呼出しによって25分遅れに変更します。

表 10.1  発車時刻の変更(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 に分を格納します。 コンストラクタも定義します。

Departure.java
/*  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 とならないように、発車時刻を出力するものです。 引数は、クラス Departured のみです。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、フィールド d . minute の値が10未満ならば、文字列"0"を追加することです。

ファイル DepartureMain.java では、メソッド delay を定義します。 このメソッドは、発車時刻を遅らせるものです。 引数は、クラス Departured と、遅れを表す minute です。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、単に d . minuteminute を計算したのでは、60以上になるかもしれませんので、60で割ってそれだけフィールド d . hour を増加させ、割った余りをフィールド d . minute に格納することです。

アルゴリズムは次のようになります。

  1. ループ制御変数 i を宣言する。
  2. 遅れを表す変数 late を宣言し、25 と初期化する。
  3. クラス Departure の配列 deps を宣言し、上記の表の通り初期化する。
  4. 文字列"Usual" と "Today" をタブで区切って出力する。
  5. 変数 i の値を 0 から電車の本数未満の間増加させながら、以下を繰り返す。{
  6.     引数を deps [ i ] としてメソッド show を呼び出す。
  7.     タブを改行なしで出力する。
  8.     引数を deps [ i ] と late としてメソッド delay を呼び出す。
  9.     引数を deps [ i ] としてメソッド show を呼び出す。
  10.     改行する。

プログラムは以下のようになります。

DepartureMain.java
/*  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%

10.3 演習10

今月A子は以下のように銀行口座のお金を出し入れしました。 思い出しますと、今月への繰り越し金は20万円でした。 通帳に記帳したときの預け入れ、引き出し、および残高の一覧を作成します。

表 10.2  お金の出し入れ
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 を定義します。 これは、残高を計算して格納するものです。 引数は、クラス Bankingb と直前の残高 balance です。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、直前の残高 balance プラス預け入れ金額 b . deposit マイナス引き出し金額 b . withdrawal の値をフィールド b . balance に格納することです。

ファイルBankingMain.javaでは、メソッド show を定義します。 これは、預け入れ金額、引き出し金額、および残高を出力するものです。 引数は、クラス Bankingb のみです。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、預け入れがあればその値を改行なしで出力し、タブ2つを改行なして出力し、引き出しがあればその値を改行なしで出力し、タブ2つを改行なしで出力し、残高を出力することです。

アルゴリズムは次のようにします。

  1. ループ制御変数 i を宣言する。
  2. 今の残高を表す変数 balanceNow を宣言し、初期値を 200000 とする。
  3. クラス Banking の配列 bankings を宣言し、上記の要領で初期化する。
  4. 文字列"Deposits", "Withdrawals", および"Balances"をタブで区切って出力する。
  5. タブ4つと変数 balanceNow の値を出力する。
  6. 変数 i の値を 0 から bankings の要素数未満の間増加させながら、以下を繰り返す。{
  7.     引数を bankings [ i ] と balanceNow としてメソッド settle を呼び出す。
  8.     フィールド bankings [ i ]. balance の値を変数 balanceNow に格納する。
  9.     引数を bankings [ i ] としてメソッド show を呼び出す。

このアルゴリズムをプログラムにしてください。

b00a001@AsiaA1:~/java% java BankingMain
Deposits        Withdrawals     Balances
                                200000
                80000           120000
30000                           150000
                50000           100000
10000                           110000
20000                           130000
b00a001@AsiaA1:~/java%

余力のある人は、同じ計算をするプログラムを、メソッドを用いないで作成してください。


10.4 レポート課題

今日の演習10に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(11/27)を明記してください。


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

2003年11月27日更新
konishi@twcu.ac.jp
Copyright (C) 2003 Zenjiro Konishi. All rights reserved.