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

情報処理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@Ampere:~/java% java TimeTest
9:30
b00a001@Ampere:~/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@Ampere:~/java% java TimeArray
13:15
14:55
16:35
b00a001@Ampere:~/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@Ampere:~/java% java TimeTest2
10:45
11:15
b00a001@Ampere:~/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@Ampere:~/java% java TimeTest3
12:30
12:30
12:30
x is equal to y
x is not equal to z
b00a001@Ampere:~/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@Ampere:~/java% java TimeTest4
13:15
13:45
b00a001@Ampere:~/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@Ampere:~/java% java TimeTest5
14:45
14:30
b00a001@Ampere:~/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 では、メソッド showdelay を定義します。

メソッド show は、例えば9時ちょうどが 9:0 とならないように、発車時刻を出力するものです。 引数は、 Departured のみです。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、 d . minute が10未満ならば"0"を追加することです。

メソッド 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*/ 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%

10.3 演習10

以下のように、数人の生年月日と2002年4月1日現在の年齢が、インスタンスの配列に格納されているとします。 メソッド呼出しによって、年齢の部分を今日(2002/11/28)現在の年齢に変更するプログラムを作成してください。

表 10.2  生年月日と年齢
生年月日 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 とします。 引数は、 Personp , 今日の年の year , 今日の月の month , および今日の日の day です。 このメソッドは値を返しません。 メソッド定義の要点は、いったん yearp . yearp . age に格納し、もしまだ誕生日をむかえていなかったら、そこから 1 減らすことです。

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

  1. ループ制御変数 i を宣言する。
  2. 今日を表す変数 yearToday , monthToday , および dayToday を宣言し、それぞれ 2002, 11, および 28 と初期化する。
  3. Person の配列の変数 pers を宣言し、上記の表の通り初期化する。
  4. "Birthdate" と "Age" をタブで区切って出力する。
  5. i の値を 0 から人数未満の間増加させながら以下を繰り返す。{
  6.   引数を pers [ i ], yearToday , monthToday , および dayToday としてメソッド setAge を呼び出す。
  7.    pers [ i ]. year , pers [ i ]. month , および pers [ i ]. day をスラッシュで区切って改行なしで出力する。
  8.   タブを改行なしで出力する。
  9.    pers [ i ]. age を出力する
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.4 レポート課題

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


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

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