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

コンピュータIIB(Javaプログラミング入門)第9回

目次
9.1 参照
9.1.1 参照とは
9.1.2 参照と配列
9.1.3 参照の操作
9.1.4 インスタンスの共有
9.2 参照とメソッド
9.2.1 参照呼出し
9.2.2 参照の使用例
9.3 演習9
9.4 レポート課題
9.5 参考文献
索引
値呼出し   共有   参照   参照呼出し  

9.1 参照

9.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*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest
9:30
b04a001@AsiaA1:~/comp2b%

このプログラムで、変数 x には何が格納されるでしょうか。 変数にはデータが一つしか格納できませんので、インスタンスそのものは入りません。 実は、 x には参照とよばれるデータが格納されます。

参照reference ) とは、メモリの中でデータが配置されている場所のことです。 変数 x には、インスタンスが置かれている「場所データ」が格納されるのです。 参照をポインタやアドレスのようなものだと思っても差し支えありません。 参照を、WWWにおけるURLのようなものだと考えてもよいでしょう。 参照は、しばしば下図のような矢印で書き表わされます。

参照のイメージ
図 9.1  参照のイメージ

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

参照の格納
参照の格納
参照の格納
図 9.2  参照の格納

9.1.2 参照と配列

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

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

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

配列への参照
図 9.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*/ }
b04a001@AsiaA1:~/comp2b% java TimeArray
13:15
14:55
16:35
b04a001@AsiaA1:~/comp2b%

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

インスタンスの配列
図 9.4  インスタンスの配列

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

配列における参照の格納
配列における参照の格納
配列における参照の格納
配列における参照の格納
図 9.5  配列における参照の格納

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

9.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*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest2
10:45
11:15
b04a001@AsiaA1:~/comp2b%
参照の変更
図 9.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*/ }
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%
インスタンスの比較
図 9.7  インスタンスの比較

9.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*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest4
13:15
13:45
b04a001@AsiaA1:~/comp2b%
インスタンスの共有
図 9.8  インスタンスの共有

9.2 参照とメソッド

9.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*/ }
b04a001@AsiaA1:~/comp2b% java TimeTest5
14:45
14:30
b04a001@AsiaA1:~/comp2b%

さて、メソッドを呼び出しますと、仮引数には実引数の値のコピーが格納されることを思い出してください。 従って、メソッドの中でコピーである仮引数を変更しても、コピー元である実引数には影響を与えないはずです。 それにもかかわらず、このプログラムでは、7行目で実引数 x が変更されたように見えます。 この現象を理解するためには、参照について意識する必要があります。

7行目で、メソッド setMinute を呼び出しますと、仮引数 t に実引数 x の値のコピーが格納されます。 x に格納されているデータは参照ですので、 t に格納されるのは参照のコピーです。 従って、 xt は同じインスタンスを共有します。 そして、メソッドが実行され、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。

インスタンス引数のメソッド呼出し
図 9.9  インスタンス引数のメソッド呼出し

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

9.2.2 参照の使用例

参照の例として、次の問題を考えます。

ある人が、ある位置から、北へ1km, 東へ1km, 東へ1km, 北へ1km, 西へ1km移動したとします。 結局、この人は、元の位置から北へ何km, 東へ何km移動したでしょうか。

この問題を、インスタンスとメソッドを用いて解決します。

ファイルPosition.javaでは、位置を格納するために、クラス Position を定義します。 フィールド north には北への移動距離を、フィールド east には東への移動距離を格納します。 コンストラクタも定義します。

Position.java
/*  1*/ class Position {
/*  2*/     int north, east;
/*  3*/     Position (int north, int east) {
/*  4*/         this.north = north;
/*  5*/         this.east = east;
/*  6*/     }
/*  7*/ }

メソッドを用いない場合、プログラムは次のようになります。

PositionMain.java
/*  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 を定義します。 これらのメソッドは、それぞれ、北、東、南、西へ移動したことを表すものです。 引数は、クラス Positionp です。 これらのメソッドは値を返しませんので、 static void と指定します。

画面出力のためのメソッドは、 printPosition を定義します。 引数は、クラス Positionp です。 このメソッドは値を返しませんので、 static void と指定します。

PositionMain2.java
/*  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*/ }

9.3 演習9

ある年のある村では、出生があり、死亡があり、死亡があり、出生があり、出生があって、結局3人出生、2人死亡だったとします。 この人口の変化を計算するプログラムを作成してください。

ファイルPopulation.javaでは、出生と死亡の人数を格納するために、クラス Population を定義します。 フィールド birth には出生人数を、 death には死亡人数を格納します。 コンストラクタは、引数が birth , death の順になるように定義します。

ファイルPopulationMain.javaでは、メソッド born を定義します。 これは、出生人数を一つ増加させるものです。 引数はクラス Populationp です。 このメソッドは値を返しませんので、 static void と指定します。

ファイルPopulationMain.javaでは、メソッド died を定義します。 これは、死亡人数を一つ増加させるものです。 引数はクラス Populationp です。 このメソッドは値を返しませんので、 static void と指定します。

ファイルPopulationMain.javaでは、メソッド printPopulation を定義します。 これは、出生と死亡の人数を画面出力するものです。 引数はクラス Populationp です。 このメソッドは値を返しませんので、 static void と指定します。

そして、ファイルPopulationMain.javaでは、次のようにメソッド borndied を呼び出して、人口の変化を計算します。

Population population = new Population(0, 0);
born(population);
died(population);
died(population);
born(population);
born(population);
printPopulation(population);
b04a001@AsiaA1:~/comp2b% java PopulationMain
3 born 2 died
b04a001@AsiaA1:~/comp2b%

余力のある人は、メソッド borndied の定義に画面出力を追加して、出生と死亡が分かるようにしてください。

b04a001@AsiaA1:~/comp2b% java PopulationMain
Somebody was born!
Somebody died!
Somebody died!
Somebody was born!
Somebody was born!
3 born 2 died
b04a001@AsiaA1:~/comp2b%

9.4 レポート課題

今日の演習9の答案(Javaプログラム)をメールで提出してください。 差出人は学内のメール・アドレス(b04a001@twcu.ac.jpなど)とし、宛先はkonishi@twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(6月22日)を明記してください。


9.5 参考文献


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

2007年6月22日更新
小西 善二郎 <konishi@twcu.ac.jp>
Copyright (C) 2007 Zenjiro Konishi. All rights reserved.