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

コンピュータ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のようなものだと考えてもよいでしょう。 参照は、しばしば下図のような矢印で書き表わされます。

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.2 参照と配列

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

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:~/comp2b% java TimeArray
13:15
14:55
16:35
b04a001@AsiaA1:~/comp2b%

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

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

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

Assignments of references
Assignments of references
Assignments of references
Assignments of references
図 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%
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:~/comp2b% java TimeTest3
12:30
12:30
12:30
x is equal to y
x is not equal to z
b04a001@AsiaA1:~/comp2b%
Equality of instances
図 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%
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:~/comp2b% java TimeTest5
14:45
14:30
b04a001@AsiaA1:~/comp2b%

さて、メソッドを呼び出しますと、仮引数には実引数の値のコピーが格納されることを思い出してください。 従って、メソッドの中でコピーである仮引数を変更しても、コピー元である実引数には影響を与えないはずです。 それにもかかわらず、このプログラムでは、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 参照の使用例

参照の例として、今年(2005年)20才、15才、10才、5才、0才の人が、それぞれ何年に生まれたかを計算しす。 話を簡単にするために、全員、今年の誕生日は過ぎているものとします。 全員の年齢と西暦をインスタンスの配列に格納し、メソッド呼出しによって、0才のときのデータに変更します。

ファイルPerson.javaでは、年齢と西暦を格納するために、 Person というクラスを定義します。 フィールド age に年齢を、フィールド year に西暦を格納します。 コンストラクタも定義します。

Person.java
/*  1*/ class Person {
/*  2*/     int age, year;
/*  3*/     Person (int age, int year) {
/*  4*/         this.age = age;
/*  5*/         this.year = year;
/*  6*/     }
/*  7*/ }

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

PersonMain.java
/*  1*/ class PersonMain {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         Person[] persons = {
/*  5*/             new Person(20, 2005),
/*  6*/             new Person(15, 2005),
/*  7*/             new Person(10, 2005),
/*  8*/             new Person(5, 2005),
/*  9*/             new Person(0, 2005)
/* 10*/         };
/* 11*/         for (i = 0; i < persons.length; i++) {
/* 12*/             System.out.print(persons[i].age);
/* 13*/             System.out.print(" years old in ");
/* 14*/             System.out.print(persons[i].year);
/* 15*/             System.out.print(", ");
/* 16*/             persons[i].year = persons[i].year - persons[i].age;
/* 17*/             persons[i].age = 0;
/* 18*/             System.out.print(persons[i].age);
/* 19*/             System.out.print(" years old in ");
/* 20*/             System.out.print(persons[i].year);
/* 21*/             System.out.println();
/* 22*/         }
/* 23*/     }
/* 24*/ }
b04a001@AsiaA1:~/comp2b% java PersonMain
20 years old in 2005, 0 years old in 1985
15 years old in 2005, 0 years old in 1990
10 years old in 2005, 0 years old in 1995
5 years old in 2005, 0 years old in 2000
0 years old in 2005, 0 years old in 2005
b04a001@AsiaA1:~/comp2b%

メソッドを用いた場合は、次のようになります。 ここでは、メソッド toBirthYear を定義します。 このメソッドは、0才のときのデータに変更するものです。 引数は、クラス Personp です。 このメソッドは値を返しませんので、 static void と指定します。

PersonMain2.java
/*  1*/ class PersonMain2 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         Person[] persons = {
/*  5*/             new Person(20, 2005),
/*  6*/             new Person(15, 2005),
/*  7*/             new Person(10, 2005),
/*  8*/             new Person(5, 2005),
/*  9*/             new Person(0, 2005)
/* 10*/         };
/* 11*/         for (i = 0; i < persons.length; i++) {
/* 12*/             System.out.print(persons[i].age);
/* 13*/             System.out.print(" years old in ");
/* 14*/             System.out.print(persons[i].year);
/* 15*/             System.out.print(", ");
/* 16*/             toBirthYear(persons[i]);
/* 17*/             System.out.print(persons[i].age);
/* 18*/             System.out.print(" years old in ");
/* 19*/             System.out.print(persons[i].year);
/* 20*/             System.out.println();
/* 21*/         }
/* 22*/     }
/* 23*/     static void toBirthYear (Person p) {
/* 24*/         p.year = p.year - p.age;
/* 25*/         p.age = 0;
/* 26*/     }
/* 27*/ }

さらに、もう一つメソッドを用いてみます。 ここでは、メソッド printPerson を定義します。 このメソッドは、西暦何年に何才かを出力するものです。 引数は、クラス Personp です。 このメソッドは値を返しませんので、 static void と指定します。

PersonMain3.java
/*  1*/ class PersonMain3 {
/*  2*/     public static void main (String[] args) {
/*  3*/         int i;
/*  4*/         Person[] persons = {
/*  5*/             new Person(20, 2005),
/*  6*/             new Person(15, 2005),
/*  7*/             new Person(10, 2005),
/*  8*/             new Person(5, 2005),
/*  9*/             new Person(0, 2005)
/* 10*/         };
/* 11*/         for (i = 0; i < persons.length; i++) {
/* 12*/             printPerson(persons[i]);
/* 13*/             System.out.print(", ");
/* 14*/             toBirthYear(persons[i]);
/* 15*/             printPerson(persons[i]);
/* 16*/             System.out.println();
/* 17*/         }
/* 18*/     }
/* 19*/     static void toBirthYear (Person p) {
/* 20*/         p.year = p.year - p.age;
/* 21*/         p.age = 0;
/* 22*/     }
/* 23*/     static void printPerson (Person p) {
/* 24*/         System.out.print(p.age);
/* 25*/         System.out.print(" years old in ");
/* 26*/         System.out.print(p.year);
/* 27*/     }
/* 28*/ }

9.3 演習9

分数 1/2, 1/3, 1/4, 2/5, 2/6 をパーセントへ変換してください。 ここで、分数をパーセントへ変換するとは、分数を 100 分のいくらにすることです。 ただし、分数によっては正確な変換はできませんので、近似値へ変換することにします。 例えば、1/2 は正確に 50/100 へ変換できますが、1/3 は近似値として 33/100 へ変換します。

ファイルFraction.javaでは、分数を格納するために、クラス Fraction を定義します。 フィールド num には分子を、 denom には分母を格納します。 コンストラクタは、引数が num , denom の順になるように定義します。

ファイルFractionMain.javaでは、次のようにコンストラクタを呼び出して、配列 fractions にすべての分数を格納します。

Fraction[] fractions = {
    new Fraction(1, 2),
    new Fraction(1, 3),
    new Fraction(1, 4),
    new Fraction(2, 5),
    new Fraction(2, 6)
};

ファイルFractionMain.javaでは、メソッド toPercent を定義します。 これは、分数をパーセントへ変換するものです。 引数はクラス Fractionf です。 このメソッドは値を返しませんので、 static void と指定します。 メソッド定義の要点は、分子の 100 倍を分母で割り、その結果を分子に格納する、100 を分母に格納する、です。

一つ一つの分数について、変換前を画面に出力し、メソッド toPercent で変換し、変換後を画面に出力するというアルゴリズムに従って、プログラムを作成してください。

b04a001@AsiaA1:~/comp2b% java FractionMain
1/2 is about 50/100
1/3 is about 33/100
1/4 is about 25/100
2/5 is about 40/100
2/6 is about 33/100
b04a001@AsiaA1:~/comp2b%

余力のある人は、分数を画面に出力する部分をメソッド printFraction にまとめ、これを呼び出してください。 引数はクラス Fractionf とします。


9.4 レポート課題

今日の演習9の答案(Javaプログラム)をメールで提出してください。 メールの送信には学内のコンピュータ(メール・サーバ)を用い、送信先はkonishi@twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(6月17日)を明記してください。


9.5 参考文献


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

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