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

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

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

9.1 参照

9.1.1 変数のスワップ

変数の値を交換することを、変数の スワップswap ) と言います。 変数 xy をスワップするプログラムを考えます。 次は間違っている例です。

/*  1*/ class BadSwap {
/*  2*/     public static void main (String[] args) {
/*  3*/         int x = 100, y = 200;
/*  4*/         x = y;
/*  5*/         y = x;
/*  6*/         System.out.println("x is " + x);
/*  7*/         System.out.println("y is " + y);
/*  8*/     }
/*  9*/ }
b04a001@AsiaA1:~/java% java BadSwap
x is 200
y is 200
b04a001@AsiaA1:~/java%

4行目を実行した段階で、 x に格納されているデータが消えてしまいますので、うまくいかないのです。

もう一つ変数 z を用意し、そこにいったん x の値をコピーしておくとうまくいきます。

/*  1*/ class GoodSwap {
/*  2*/     public static void main (String[] args) {
/*  3*/         int x = 100, y = 200, z;
/*  4*/         z = x;
/*  5*/         x = y;
/*  6*/         y = z;
/*  7*/         System.out.println("x is " + x);
/*  8*/         System.out.println("y is " + y);
/*  9*/     }
/* 10*/ }
b04a001@AsiaA1:~/java% java GoodSwap
x is 200
y is 100
b04a001@AsiaA1:~/java%

9.1.2 参照とは

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

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

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

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

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

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

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

9.1.3 参照と配列

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

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

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

A reference to an array
図 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:~/java% java TimeArray
13:15
14:55
16:35
b04a001@AsiaA1:~/java%

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

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

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

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

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

9.1.4 参照の操作

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

次のプログラムでは、まず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:~/java% java TimeTest2
10:45
11:15
b04a001@AsiaA1:~/java%
Changing references
図 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:~/java% java TimeTest3
12:30
12:30
12:30
x is equal to y
x is not equal to z
b04a001@AsiaA1:~/java%
Equality of instances
図 9.7  インスタンスの比較

9.1.5 インスタンスの共有

次のプログラムにおいて、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:~/java% java TimeTest4
13:15
13:45
b04a001@AsiaA1:~/java%
Sharing an instance
図 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:~/java% java TimeTest5
14:45
14:30
b04a001@AsiaA1:~/java%

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

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

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

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

9.2.2 参照の使用例

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

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

9.3 演習9

次の5つの直角三角形の面積を求めます。

表 9.2  直角三角形ABCの辺の長さ
No. AB BC CA
0 3 5 4
1 8 6 10
2 12 13 5
3 15 9 12
4 26 24 10

三角形は、三辺の長さが決まれば形が決まります。 特に、直角三角形の場合は、最も長い辺以外の二辺が直角を挟みます。 また、直角三角形の面積は、直角を挟む二辺の長さの積÷2です。 したがって、直角三角形が与えられた場合、辺CAが最も長くなるように辺の長さを入れ替え、辺ABと辺BCを掛けて2で割れば面積になるわけです。 画面には、辺の長さを入れ替えた後の辺の長さと面積を出力します。

Arrengement of a triangle
図 9.10  直角三角形の辺の入れ替え

ファイルTriangle.javaでは、直角三角形の辺の長さを格納するために、クラス Triangle を定義します。 フィールド AB , BC , CA には辺の長さを格納します。 コンストラクタは、引数が AB , BC , CA の順になるように定義します。

ファイルTriangleMain.javaでは、次のようにコンストラクタを呼び出して、配列 tris にすべての直角三角形を格納します。

Triangle[] tris = {new Triangle(3, 5, 4),
                   new Triangle(8, 6, 10),
                   new Triangle(12, 13, 5),
                   new Triangle(15, 9, 12),
                   new Triangle(26, 24, 10)};

ファイルTriangleMain.javaでは、メソッド arrange を定義します。 これは、辺の長さを入れ替えて、最長のものをCAにするものです。 引数はクラス Trianglet です。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、スワップ用の変数 temp を宣言する。 もし t . ABt . BC より大きいならば、この二つをスワップする。 そして、もし t . BCt . CA より大きいならば、この二つをスワップする、です。

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

  1. ループ制御変数 i を宣言する。
  2. クラス Triangle の配列 tris を宣言し、上記の要領で初期化する。
  3. 文字列"No.", "AB", "BC", "CA", および"Area"をタブで区切って出力する。
  4. 変数 i の値を 0 から tris の要素数未満の間増加させながら、以下を繰り返す。{
  5.     引数を tris [ i ] としてメソッド arrange を呼び出す。
  6.     式 i の値とタブを改行なしで出力する。
  7.     式 tris [ i ]. AB の値とタブを改行なしで出力する。
  8.     式 tris [ i ]. BC の値とタブを改行なしで出力する。
  9.     式 tris [ i ]. CA の値とタブを改行なしで出力する。
  10.     式 tris [ i ]. AB × tris [ i ]. BC /2の値を出力する。

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

b04a001@AsiaA1:~/java% java TriangleMain
No.     AB      BC      CA      Area
0       3       4       5       6
1       6       8       10      24
2       12      5       13      30
3       9       12      15      54
4       24      10      26      120
b04a001@AsiaA1:~/java%

余力のある人は、画面出力の部分(ただし番号以外)をメソッド show にまとめ、これを呼び出してください。 引数はクラス Trianglet とします。


9.4 レポート課題

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


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

2004年6月18日更新
konishi@twcu.ac.jp
Copyright (C) 2004 Zenjiro Konishi. All rights reserved.