目次 | 索引 |
---|---|
これまで扱った配列は、要素が整数(
int
型)のものだけでした。
以下のようにしますと、要素がクラスのインスタンスである配列が使えます。
要点は、これまで
int
と書いていた部分に、クラス名
classname
を書くことです。
まず、変数の宣言は
classname[] arrayname;
と書きます。 大きさが size である配列を生成して変数 arrayname に格納するには次のようにします。
arrayname = new classname[size];
このふたつは次のようにまとめられます。
classname[] arrayname = new classname[size];
この段階では、まだ配列の要素には何も格納されていません。 インスタンスを生成して、配列の各要素に格納する必要があります。 これは、例えば次のようにします。
int i; ... for (i = 0; i < arrayname.length; i++) { arrayname[i] = new classname(); }
配列の要素に格納されたインスタンスのフィールドへの代入は、文
arrayname[expression].fieldname = expression2;
によって行います。 式
arrayname[expression].fieldname
によってこの値が取り出せます。
次のプログラムは、配列に3つの時刻データを格納し、それを画面に出力するものです。
4行目で、
Time
クラスの配列の変数
a
を宣言します。
5行目で配列を生成して
a
に代入し、7行目でインスタンスを生成して配列の各要素
a[i]
に代入します。
これで、インスタンスのフィールドに整数が代入できます。
8行目と9行目でそこに式の値を格納し、12行目でそれを取り出します。
/* 1*/ class Time { /* 2*/ int hour, minute; /* 3*/ }
/* 1*/ class TimeArray { /* 2*/ public static void main (String[] args) { /* 3*/ int i; /* 4*/ Time[] a; /* 5*/ a = new Time[3]; /* 6*/ for (i = 0; i < a.length; i++) { /* 7*/ a[i] = new Time(); /* 8*/ a[i].hour = (13 * 60 + 15 + 100 * i) / 60; /* 9*/ a[i].minute = (13 * 60 + 15 + 100 * i) % 60; /*10*/ } /*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%
変数に配列を代入しますと、実際には変数に配列への参照が格納されました。 2次元配列の場合、2次元配列への参照が格納されるわけではありません。 やはり配列への参照が格納されます。 ただしその配列は、要素が配列であるものです。 2次元配列は、本当は配列の配列なのです。
例えば、初期化
int[][] a = {{98, 91, 84, 77}, {70, 63, 56, 49}, {42, 35, 28, 21}};
によって生成された2次元配列は、通常次のようなイメージで表されます。
より正確なイメージは次の通りです。
これまで定義したメソッドは、引数や返り値が整数(
int
型)のものだけでした。
(引数や返り値がないものもありました。)
これまで
int
と書いていた部分にクラス名を書けば、引数や返り値がインスタンスであるメソッドが定義できます。
例えば、引数が
Time
クラスのインスタンスで、返り値が整数(
int
型)である(クラス)メソッドは、
static int methodname (Time argument) { ... }
と定義します。
また、引数が
Time
クラスのインスタンスと整数(
int
型)で、値を返さない(クラス)メソッドは、
static void methodname (Time argument1, int argument2) { ... }
と定義します。
次のプログラムでは、引数がインスタンスであるメソッドをいくつか定義しています。
getTimeHour
は、
Time
クラスのインスタンスを引数にとり、その
hour
フィールドの値を返します。
getTimeMinute
は、同様に
minute
フィールドの値を返します。
setTimeHour
は、
Time
クラスのインスタンスと整数(
int
型)を引数にとり、値を返さないメソッドです。
そのインスタンスの
hour
フィールドに、その整数を代入します。
setTimeMinute
は、同じく
minute
フィールドに代入します。
4行目と5行目で、インスタンス
x
が14時45分を表すようにします。
7行目で、
x
の
minute
フィールドの値が30に変更されます。
従って、
x
は14時30分を表すようになります。
/* 1*/ class Time { /* 2*/ int hour, minute; /* 3*/ }
/* 1*/ class TimeTest5 { /* 2*/ public static void main (String[] args) { /* 3*/ Time x = new Time(); /* 4*/ setTimeHour(x, 14); /* 5*/ setTimeMinute(x, 45); /* 6*/ System.out.println(getTimeHour(x) + ":" + getTimeMinute(x)); /* 7*/ setTimeMinute(x, 30); /* 8*/ System.out.println(getTimeHour(x) + ":" + getTimeMinute(x)); /* 9*/ } /*10*/ static int getTimeHour (Time t) { /*11*/ return t.hour; /*12*/ } /*13*/ static int getTimeMinute (Time t) { /*14*/ return t.minute; /*15*/ } /*16*/ static void setTimeHour (Time t, int hour) { /*17*/ t.hour = hour; /*18*/ } /*19*/ static void setTimeMinute (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行目で、メソッド
setTimeMinute
が呼び出されますと、仮引数
t
に実引数
x
の値が格納されます。
詳しく言いますと、
x
に格納されているデータ(すなわち参照)のコピーが
t
に格納されます。
これで、
x
と
t
は同じインスタンスを共有します。
メソッドの定義にしたがい、この共有しているインスタンスは変更されます。
このようにして、インスタンス
x
は、14時45分から14時30分に変更されるのです。
仮引数に実引数の値のコピーを格納する呼び出し方を、 値呼出し ( call by value )とよびます。 また、仮引数への参照を実引数への参照と一致させる呼び出し方を、 参照呼出し ( call by reference )とよびます。 Javaのメソッド呼び出しは値呼出しです。 しかし、引数がクラスのインスタンスですと、仮引数に参照のコピーが格納されますので、実質的に参照呼出しになるのです。
以前、メソッドにはインスタンスメソッドとクラスメソッドがあると言いました。 これまではクラスメソッドのみを使ってきましたが、Javaらしいプログラムではインスタンスメソッドのほうがよく用いられます。 クラスメソッドと比較しながら、インスタンスメソッドについて説明します。
上記のプログラム(Time.javaとTimeTest5.java)をインスタンスメソッドを用いて書きなおしますと、次のようになります。
/* 1*/ class FormalTime { /* 2*/ int hour, minute; /* 3*/ int getHour () { /* 4*/ return this.hour; /* 5*/ } /* 6*/ int getMinute () { /* 7*/ return this.minute; /* 8*/ } /* 9*/ void setHour (int hour) { /*10*/ this.hour = hour; /*11*/ } /*12*/ void setMinute (int minute) { /*13*/ this.minute = minute; /*14*/ } /*15*/ }
/* 1*/ class FormalTimeTest { /* 2*/ public static void main (String[] args) { /* 3*/ FormalTime x = new FormalTime(); /* 4*/ x.setHour(14); /* 5*/ x.setMinute(45); /* 6*/ System.out.println(x.getHour() + ":" + x.getMinute()); /* 7*/ x.setMinute(30); /* 8*/ System.out.println(x.getHour() + ":" + x.getMinute()); /* 9*/ } /*10*/ }
b00a001@Ampere:~/java% java FormalTimeTest 14:45 14:30 b00a001@Ampere:~/java%
まず、メソッド定義を実行プログラム(
main
)には並べて書かず、フィールド定義に並べて書きます。
レコード型としてのクラスはフィールドのみを定義しますが、一般のクラスはさらにメソッドも定義します。
メソッドは、実行プログラムよりもデータ(インスタンス)により強く結び付くと考えます。
次に、キーワード
static
を取り除きます。
メソッド定義で
static
と書くとクラスメソッド、書かないとインスタンスメソッドになります。
メソッド呼び出しの形も変えます。 値を返さないインスタンスメソッドを呼び出すには、文
instance.methodname(argument, ...);
を用います。 ここで instance は、インスタンスを値とする式です。 例えば、インスタンス(への参照)が格納された変数などです。 値を返すインスタンスメソッドを呼び出すには、式
instance.methodname(argument, ...)
を用います。 なお、括弧がなければインスタンスのフィールドからデータを取り出す形になります。
この式
instance
は、メソッド呼び出しの実引数としての役割を果たします。
この実引数に対応する仮引数は、メソッド定義でのキーワード
this
です。
メソッド呼び出しの際、
this
にはインスタンス
instance
への参照がコピーされます。
インスタンスメソッドとは、引数の中からインスタンスを一つ選び、それを特別扱いしたものと考えてください。
特別扱いされたインスタンスは、実引数としては、括弧の中ではなく、メソッド名の左に書かれます。
仮引数としては、括弧の中では定義されず、キーワード
this
で表されます。
この、一つのインスタンスを特別扱いすることは、
メッセージ受渡しモデル
(
message-passing model
)に基づいています。
これは、データ(インスタンス)がメッセージを受け取ると、それに応じて操作(メソッド)が引き起こされるという考え方です。
Javaでのメッセージは、メソッド名のことだと思ってください。
インスタンスにメッセージを送りますと、そのインスタンスのクラスで定義されたメソッドが起動されるということです。
メッセージ受渡しモデルに従いますと、上記のメソッド呼び出しは、インスタンス
instance
にメッセージ
methodname
(
argument
,
...
)
を送ることに相当します。
インスタンスメソッドにできて、クラスメソッドにできないことのひとつに、メソッド定義で
this
参照を用いることがあげられます。
this
参照は、インスタンスメソッドの呼び出しのときにメッセージを受け取ったインスタンスを指します。
クラスメソッドでは、メッセージを受け取るインスタンスが存在しませんので、
this
参照が使えないのです。
学術雑誌のページ配分を行なうJavaアプリケーションを作成してください。 この雑誌には、すべての記事は奇数ページから始まるという規則があります。 ページ数が奇数の記事については、記事の最後に空白のページを追加して、次の記事も規則通りになるようにします。
今、次の表のような記事が集まり、この順で掲載することになりました。
著者名 | ページ数 |
---|---|
Sato | 50 |
Suzuki | 55 |
Yamada | 52 |
プログラムの実行結果は次の通りです。
b00a001@Ampere:~/java% java ArticleTest Sato: pp. 1--50 Suzuki: pp. 51--105 Yamada: pp. 107--158 b00a001@Ampere:~/java%
これは、著者Satoの記事がページ1からページ50までという意味です。 ページ106が空白になっていることに注意してください。
プログラムは穴埋めとします。 コメントを参考にして、???の部分を正しいものにしてください。
class Article { ??? // 最初のページと最後のページのフィールド(例え ??? // ば first と last)を定義する。 void fixPageRange (int length, int first) { ??? // インスタンス this のフィールド(最初のペ ??? // ージと最後のページ)に値を代入する。仮引 ??? // 数 length は記事のページ数、仮引数 first ??? // は最初のページ。 } int nextFirstPage () { ??? // インスタンス this の次の記事の最初のペー ??? // ジを返す。空白ページを入れるかどうかは、 ??? // インスタンス this の最後のページが偶数か ??? // どうか調べると分かる。 } void printPageRange () { ??? // インスタンス this の最初のページと最後の ??? // ページを画面に出力する。 } }
class ArticleTest { public static void main (String[] args) { Article sato = new Article(); Article suzuki = new Article(); Article yamada = new Article(); int p = 1; sato.fixPageRange(50, p); p = sato.nextFirstPage(); suzuki.fixPageRange(55, p); p = suzuki.nextFirstPage(); yamada.fixPageRange(52, p); System.out.print("Sato: "); sato.printPageRange(); System.out.println(); System.out.print("Suzuki: "); suzuki.printPageRange(); System.out.println(); System.out.print("Yamada: "); yamada.printPageRange(); System.out.println(); } }
今日の演習9に従ってJavaプログラムを作成し、そのプログラムをkonishi@twcu.ac.jpあてにメールで提出してください。 メールには、学生番号、氏名、科目名、授業日(11/15)を明記してください。