目次 | 索引 |
---|---|
前回の授業では、次のようなプログラムを動かしました。
/* 1*/ class Time { /* 2*/ int hour, minute; /* 3*/ }
/* 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のようなものだと考えてもよいでしょう。
参照は、しばしば下図のような矢印で書き表わされます。
上記のプログラムは、変数を宣言し、インスタンスを生成してその変数に代入し、そしてそのフィールドに数を代入しています。 この処理の流れを順に表現したのが以下の図です。
インスタンスと同様、変数に配列を代入しましても、その変数にはその配列への参照が格納されます。 例えば、
int[] a = {91, 74, 57, 40, 23};
のように配列を初期化しますと、次の図のようになります。
前回の授業では、以下のようなプログラムも紹介しました。 これがインスタンスの配列を扱っていることを思い出してください。
/* 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%
変数にインスタンスの配列を代入しますと、配列への参照とインスタンスへの参照が組み合わさった参照構造が得られます。 上記の場合ですと、以下のようになります。
この参照構造ができあがるまでの過程は次の通りです。 インスタンスの配列が利用できるようになるまでには、多くのステップが必要であることを理解してください。
以下では、主にインスタンスへの参照について説明します。 配列への参照についても同じことが言えます。
参照が格納される変数の値を変えることは、参照先を変更することになります。
次のプログラムでは、まず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%
参照が格納される変数には、新しく生成されるインスタンスだけではなく、すでに生成されたインスタンスも代入できます。 この場合、その変数にはそのインスタンスへの参照のコピーが格納されます。 インスタンスそのものはコピーされません。
また、整数(
int
型)どうしでは、関係演算子として
>
や
==
などが使えましたが、インスタンスどうしでは
==
が使えます。
これは参照先が一致していることを意味します。
フィールドの値がすべて等しくても、置かれている場所が異なれば、インスタンスは(
==
の意味で)等しくありません。
次のプログラムは、まず変数
x
に12時30分を表すインスタンスへの参照を格納します。
そして、変数
y
にはそのインスタンスを直接代入し、変数
z
にはフィールドごとに代入します。
x
と
y
は等しいインスタンスだと判定されますが、
x
と
z
は等しくないと判定されます。
/* 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%
次のプログラムにおいて、7行目の代入で変数
x
と
y
は同じインスタンスを参照します。
この状態を、
x
と
y
はインスタンスを
共有
(
share
)
しているといいます。
インスタンスを共有している場合、一方からインスタンスを変更しますと、他方からも変更されたことになります。
9行目で、インスタンス
x
の
minute
フィールドを変更しますと、インスタンス
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%
これまで定義したメソッドは、引数や返り値が整数(
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行目で、
x
の
minute
フィールドの値が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
に格納されるのは参照のコピーです。
従って、
x
と
t
は同じインスタンスを共有します。
そして、メソッドが実行され、この共有しているインスタンスは変更されます。
このようにして、インスタンス
x
は、14時45分から14時30分に変更されるのです。
仮引数に実引数の値のコピーを格納する呼び出し方を、 値呼出し ( call by value ) とよびます。 また、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出し ( call by reference ) とよびます。 Javaのメソッド呼出しは値呼出しです。 しかし、引数がインスタンスですと、仮引数に参照のコピーが格納されますので、実質的に参照呼出しになるわけです。
参照の例として、今年(2005年)20才、15才、10才、5才、0才の人が、それぞれ何年に生まれたかを計算しす。 話を簡単にするために、全員、今年の誕生日は過ぎているものとします。 全員の年齢と西暦をインスタンスの配列に格納し、メソッド呼出しによって、0才のときのデータに変更します。
ファイルPerson.javaでは、年齢と西暦を格納するために、
Person
というクラスを定義します。
フィールド
age
に年齢を、フィールド
year
に西暦を格納します。
コンストラクタも定義します。
/* 1*/ class Person { /* 2*/ int age, year; /* 3*/ Person (int age, int year) { /* 4*/ this.age = age; /* 5*/ this.year = year; /* 6*/ } /* 7*/ }
メソッドを用いないと、プログラムは次のようになります。
/* 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才のときのデータに変更するものです。
引数は、クラス
Person
の
p
です。
このメソッドは値を返しませんので、
static void
と指定します。
/* 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
を定義します。
このメソッドは、西暦何年に何才かを出力するものです。
引数は、クラス
Person
の
p
です。
このメソッドは値を返しませんので、
static void
と指定します。
/* 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*/ }
分数 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
を定義します。
これは、分数をパーセントへ変換するものです。
引数はクラス
Fraction
の
f
です。
このメソッドは値を返しませんので、
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
にまとめ、これを呼び出してください。
引数はクラス
Fraction
の
f
とします。
今日の演習9の答案(Javaプログラム)をメールで提出してください。 メールの送信には学内のコンピュータ(メール・サーバ)を用い、送信先はkonishi@twcu.ac.jpとします。 メールの本文には、学生番号、氏名、科目名、授業日(6月17日)を明記してください。