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

情報処理IIIA(Javaプログラミング入門)第9回

目次 索引
9.1 データ型と参照
9.1.1 インスタンスの配列
9.1.2 2次元配列
9.2 メソッドと参照
9.2.1 値呼出しと参照呼出し
9.2.2 メッセージ受渡しモデル
9.3 演習9
9.4 レポート課題

9.1 データ型と参照

9.1.1 インスタンスの配列

これまで扱った配列は、要素が整数( 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行目でそれを取り出します。

Time.java
/* 1*/ class Time {
/* 2*/     int hour, minute;
/* 3*/ }
TimeArray.java
/* 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%
An array of instances
図 9.1  インスタンスの配列

9.1.2 2次元配列

変数に配列を代入しますと、実際には変数に配列への参照が格納されました。 2次元配列の場合、2次元配列への参照が格納されるわけではありません。 やはり配列への参照が格納されます。 ただしその配列は、要素が配列であるものです。 2次元配列は、本当は配列の配列なのです。

例えば、初期化

int[][] a = {{98, 91, 84, 77},
             {70, 63, 56, 49},
             {42, 35, 28, 21}};

によって生成された2次元配列は、通常次のようなイメージで表されます。

A two-dimensional array
図 9.2  2次元配列

より正確なイメージは次の通りです。

An array of arrays
図 9.3  配列の配列

9.2 メソッドと参照

9.2.1 値呼出しと参照呼出し

これまで定義したメソッドは、引数や返り値が整数( 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行目で、 xminute フィールドの値が30に変更されます。 従って、 x は14時30分を表すようになります。

Time.java
/* 1*/ class Time {
/* 2*/    int hour, minute;
/* 3*/ }
TimeTest5.java
/* 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 に格納されます。 これで、 xt は同じインスタンスを共有します。 メソッドの定義にしたがい、この共有しているインスタンスは変更されます。 このようにして、インスタンス x は、14時45分から14時30分に変更されるのです。

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

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

9.2.2 メッセージ受渡しモデル

以前、メソッドにはインスタンスメソッドとクラスメソッドがあると言いました。 これまではクラスメソッドのみを使ってきましたが、Javaらしいプログラムではインスタンスメソッドのほうがよく用いられます。 クラスメソッドと比較しながら、インスタンスメソッドについて説明します。

上記のプログラム(Time.javaとTimeTest5.java)をインスタンスメソッドを用いて書きなおしますと、次のようになります。

FormalTime.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*/ }
FormalTimeTest.java
/* 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 参照が使えないのです。


9.3 演習9

学術雑誌のページ配分を行なうJavaアプリケーションを作成してください。 この雑誌には、すべての記事は奇数ページから始まるという規則があります。 ページ数が奇数の記事については、記事の最後に空白のページを追加して、次の記事も規則通りになるようにします。

今、次の表のような記事が集まり、この順で掲載することになりました。

表 9.1  記事の著者名とページ数
著者名 ページ数
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が空白になっていることに注意してください。

プログラムは穴埋めとします。 コメントを参考にして、???の部分を正しいものにしてください。

Article.java
class Article {
    ??? // 最初のページと最後のページのフィールド(例え
    ??? // ば first と last)を定義する。
    void fixPageRange (int length, int first) {
        ??? // インスタンス this のフィールド(最初のペ
        ??? // ージと最後のページ)に値を代入する。仮引
        ??? // 数 length は記事のページ数、仮引数 first
        ??? // は最初のページ。
    }
    int nextFirstPage () {
        ??? // インスタンス this の次の記事の最初のペー
        ??? // ジを返す。空白ページを入れるかどうかは、
        ??? // インスタンス this の最後のページが偶数か
        ??? // どうか調べると分かる。
    }
    void printPageRange () {
        ??? // インスタンス this の最初のページと最後の
        ??? // ページを画面に出力する。
    }
}
ArticleTest.java
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.4 レポート課題

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


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

2001年11月15日更新
konishi@twcu.ac.jp
Copyright (C) 2001 Zenjiro Konishi. All rights reserved.