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

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

目次 索引
10.1 クラスの構成
10.1.1 Javaプログラムの構成
10.1.2 コンストラクタ
10.1.3 大域変数としてのクラス変数
10.1.4 定数としてのクラス変数
10.1.5 クラスメソッドのモジュール化
10.2 演習10
10.3 レポート課題

10.1 クラスの構成

10.1.1 Javaプログラムの構成

これまで、Javaのプログラムをいろいろ作成してきました。 その中には、他の.classファイルがないと実行できないものもありました。 実は、Javaのプログラムとは、クラス定義の集まりです。 そして、クラス定義は一般的に次のような形になります。

class classname {
    class variable declarations

    field declarations

    constructor definitions

    class method definitions

    instance method definitions
}

ここで、クラス変数とコンストラクタについてはまだ触れていません。 以下で説明します。

10.1.2 コンストラクタ

クラスのインスタンスを扱う場合、これまでは、生成してからフィールドに値を格納しました。 例えば次のように書きました。

Time x = new Time();
x.hour = 13;
x.minute = 15;

変数や配列では、初期化を使ってプログラムを短くしました。 インスタンスでも、 コンストラクタconstructor )というものを用いますと、初期化に相当することができます。 上記の例ですと、例えば次のように書けます。

Time x = new Time(13, 15);

コンストラクタはメソッドに似ています。 どちらも、あらかじめ定義しておき、呼び出しによってその機能が働きます。 コンストラクタとメソッドは以下の点が異なります。

次のプログラムでは、コンストラクタを2つ定義しています。 OpeningTime.javaの3行目から6行目までは、第1引数を hour フィールドに格納し、第2引数を minute フィールドに格納するコンストラクタです。 7行目から10行目までは、 hour フィールドに9を格納し、 minute フィールドに0を格納するコンストラクタです。 特に断らなければ、 OpeningTime クラスのインスタンスは9時を表すということです。

OpeningTime.java
/* 1*/ class OpeningTime {
/* 2*/     int hour, minute;
/* 3*/     OpeningTime (int h, int m) {
/* 4*/         this.hour = h;
/* 5*/         this.minute = m;
/* 6*/     }
/* 7*/     OpeningTime () {
/* 8*/         this.hour = 9;
/* 9*/         this.minute = 0;
/*10*/     }
/*11*/     void print () {
/*12*/         System.out.print(this.hour);
/*13*/         if (this.minute < 10) {
/*14*/             System.out.println(":0" + this.minute);
/*15*/         } else {
/*16*/             System.out.println(":" + this.minute);
/*17*/         }
/*18*/     }
/*19*/ }
OpeningTimeTest.java
/* 1*/ class OpeningTimeTest {
/* 2*/     public static void main (String[] args) {
/* 3*/         OpeningTime x = new OpeningTime(9, 30);
/* 4*/         OpeningTime y = new OpeningTime();
/* 5*/         x.print();
/* 6*/         y.print();
/* 7*/     }
/* 8*/ }
b00a001@Ampere:~/java% java OpeningTimeTest
9:30
9:00
b00a001@Ampere:~/java%

コンストラクタをひとつも定義しない場合は、デフォルトのコンストラクタ classname () が用意されます。 実は、これはインスタンスの生成のときにこれまで用いてきたものです。 クラスを設計するとき、普通はコンストラクタを用意します。

10.1.3 大域変数としてのクラス変数

これまで説明した通り、変数は宣言すれば使えます。 しかし、いつまでも使い続けられるわけではありません。 例えば、メソッド定義

static void printStars (int m) {
    int i;
    for (i = 0; i < m; i++) {
        System.out.print("*");
    }
}

での変数 i は、メソッド呼び出しの際に用意されます。 しかし、この呼び出しが終了しますと、変数 i は使えなくなります。 このような変数は 局所変数local variable )とよばれます。

データを局所変数に格納しても、他のメソッドからはそのデータは利用できません。 複数のメソッドの間でデータを共有するには、 大域変数global variable )というものを用います。 大域変数とは、プログラムのどこからでも使える変数です。 あるメソッドで大域変数にデータを格納し、他のメソッドでそれを取り出すことができます。

実は、Javaには大域変数は用意されていません。 これは、 クラス変数class variable )というもので代用します。 クラス変数とは、クラスに割り当てられる変数と考えてください。 クラス変数と対照的なのがインスタンス変数(すなわちフィールド)です。 クラスのインスタンスを生成しますと、インスタンスごとにインスタンス変数が用意されます。 一方、クラス変数は、インスタンスをいくら生成しても、クラスにひとつだけ用意されます。 次の図は、クラス変数とインスタンス変数のイメージです。

図 10.1  クラス変数とインスタンス変数

次のプログラムは、出勤時刻に関して遅刻の人数を数えるものです。 AttendingTime.javaの3行目で、クラス変数 lateCount を用意します。 クラス変数は、フィールドと同じように指定します。 違いは、キーワード static を書くところです。

AttendingTime.java
/* 1*/ class AttendingTime {
/* 2*/     int hour, minute;
/* 3*/     static int lateCount = 0;
/* 4*/     AttendingTime (int h, int m) {
/* 5*/         this.hour = h;
/* 6*/         this.minute = m;
/* 7*/     }
/* 8*/     void checkLate () {
/* 9*/         if (540 < 60 * this.hour + this.minute) {
/*10*/             lateCount++;
/*11*/         }
/*12*/     }
/*13*/     int getLateCount () {
/*14*/         return lateCount;
/*15*/     }
/*16*/ }
AttendingTimeTest.java
/* 1*/ class AttendingTimeTest {
/* 2*/     public static void main (String[] args) {
/* 3*/         AttendingTime sato = new AttendingTime(8, 55);
/* 4*/         sato.checkLate();
/* 5*/         AttendingTime suzuki = new AttendingTime(9, 10);
/* 6*/         suzuki.checkLate();
/* 7*/         AttendingTime yamada = new AttendingTime(9, 5);
/* 8*/         yamada.checkLate();
/* 9*/         System.out.println(yamada.getLateCount());
/*10*/     }
/*11*/ }
b00a001@Ampere:~/java% java AttendingTimeTest
2
b00a001@Ampere:~/java%

10.1.4 定数としてのクラス変数

大域変数を用いて 定数constant )を表すことがよくあります。 定数とは、常に変わらない値のことです。 プログラムに定数を直接書きますと、それが何を表しているのか分かりにくいものです。 そこで、大域変数に定数を代入しておき、定数の代わりに変数名を書きます。 定数に分かりやすい名前をつけ、プログラムを読みやすくするのです。

定数を表す変数へは、新たな代入が行われないようにしなくてはいけません。 変数を宣言するときに、キーワード final を書きますと、値の変更が禁止されます。 これによって、どの変数が定数を表すのかが明確になります。

上記のプログラムでは、AttendingTime.java の9行目の条件

540 < 60 * this.hour + this.minute

540 が何を表しているのかが分かりにくいでした。 これは始業時刻9:00の絶対時刻(分)です。 定数を

final static int OPENINGHOUR = 9, OPENINGMINUTE = 0;

と定め、条件を

60 * OPENINGHOUR + OPENINGMINUTE
    < 60 * this.hour + this.minute

と書きますと、出勤時刻が始業時刻以後ならば…と読めるわけです。

AttendingTime.java(第2版)
/* 1*/ class AttendingTime {
/* 2*/     int hour, minute;
/* 3*/     static int lateCount = 0;
/* 4*/     final static int OPENINGHOUR = 9, OPENINGMINUTE = 0;
/* 5*/     AttendingTime (int h, int m) {
/* 6*/         this.hour = h;
/* 7*/         this.minute = m;
/* 8*/     }
/* 9*/     void checkLate () {
/*10*/         if (60 * OPENINGHOUR + OPENINGMINUTE
/*11*/                 < 60 * this.hour + this.minute) {
/*12*/             lateCount++;
/*13*/         }
/*14*/     }
/*15*/     int getLateCount () {
/*16*/         return lateCount;
/*17*/     }
/*18*/ }

10.1.5 クラスメソッドのモジュール化

クラス変数とクラスメソッドは、これまで同じクラスのものしか使いませんでした。 Javaでは、他のクラスのクラス変数やクラスメソッドを用いることもできます。 関係の深いクラス変数やクラスメソッドをひとまとめにしてクラスを定義し、他のクラスからそれらを利用するわけです。 ソフトウェアの設計法では、関係の深い機能同士をひとまとめにしたプログラム単位を モジュールmodule )とよびます。 Javaのクラスは、モジュールとしての役割も果たすのです。

他のクラス class のクラス変数 variable は、 class . variable と書くと使えます。 同様に、他のクラス class のクラスメソッド method ( ... ) は、 class . method ( ... ) と書くと使えます。

上記のプログラムでは、遅刻の人数を表示するのに、 getLateCount メソッドを用いて

System.out.println(yamada.getLateCount());

と書きました。 遅刻の人数を求めるのに、インスタンス yamada にメッセージを送るのは不自然です。 このメソッドは、定義

int getLateCount () {
    return lateCount;
}

を見ると分かりますが、単にクラス変数 lateCount の値を返すだけで、インスタンス yamada のデータは使っていません。 そこで、クラスメソッドとして

static int getLateCount () {
    return lateCount;
}

と定義し直すことにします。 このメソッドは、これまでのクラスメソッドのように

System.out.println(getLateCount());

と書いても呼び出せません。 メソッドが定義されているクラス( AttendingTime )と呼び出すクラス( AttendingTimeTest )が異なるからです。

System.out.println(AttendingTime.getLateCount());

と書くと呼び出せます。

AttendingTime.java(第3版)
/* 1*/ class AttendingTime {
/* 2*/     int hour, minute;
/* 3*/     static int lateCount = 0;
/* 4*/     final static int OPENINGHOUR = 9, OPENINGMINUTE = 0;
/* 5*/     AttendingTime (int h, int m) {
/* 6*/         this.hour = h;
/* 7*/         this.minute = m;
/* 8*/     }
/* 9*/     void checkLate () {
/*10*/         if (60 * OPENINGHOUR + OPENINGMINUTE
/*11*/                 < 60 * this.hour + this.minute) {
/*12*/             lateCount++;
/*13*/         }
/*14*/     }
/*15*/     static int getLateCount () {
/*16*/         return lateCount;
/*17*/     }
/*18*/ }
AttendingTimeTest.java
/* 1*/ class AttendingTimeTest {
/* 2*/     public static void main (String[] args) {
/* 3*/         AttendingTime sato = new AttendingTime(8, 55);
/* 4*/         sato.checkLate();
/* 5*/         AttendingTime suzuki = new AttendingTime(9, 10);
/* 6*/         suzuki.checkLate();
/* 7*/         AttendingTime yamada = new AttendingTime(9, 5);
/* 8*/         yamada.checkLate();
/* 9*/         System.out.println(AttendingTime.getLateCount());
/*10*/     }
/*11*/ }

なお、遅刻の人数を求めるのに、メソッド getLateCount を用いず、クラス変数 lateCount の値を直接取り出す方法もあります。 この場合でも、クラスが異なる理由から、

System.out.println(lateCount);

では値は取り出せません。

System.out.println(AttendingTime.lateCount);

と書くと取り出せます。

ここで、インスタンスメソッドとクラスメソッドの違いをまとめます。

インスタンスメソッドとクラスメソッドのどちらを使うべきかは一概には言えません。


10.2 演習10

比例代表制の選挙で、得票数から議席数を計算するJavaアプリケーションを作成してください。 比例代表制とは、各政党の得票数に応じて全議席を比例配分する選挙制度です。 この配分方式として、ドント方式というものを用いることにします。 例に基づいて、このドント方式を説明します。

表 10.1  ドント方式による議席配分(括弧内は順番、全議席数は4、当選は丸括弧)
政党A 政党B 政党C
得票数 900 420 240
議席数 3 1 0
1 (1) 900/1=900 (3) 420/1=420 [5] 240/1=240
2 (2) 900/2=450 [7] 420/2=210 -- 240/2=120
3 (4) 900/3=300 -- 420/3=140 -- 240/3=80
4 [6] 900/4=225 -- 420/4=105 -- 240/4=60

ドント方式では、まず、得票数を1で割った商、2で割った商、3で割った商、…を、政党ごとに求めます。 そして、これらの商に対して大きい順に番号をつけます。 この番号順に、その政党の議席数を一つ増やします。 番号が全議席数に達したら終了します。 このときの議席数が、最終的な議席数になります。

この例では、政党Aが1番目、2番目、および4番目の商によって、議席数3となります。 政党Bは、1で割った商が3番目になって、1議席です。 政党Cは、1で割った商が4番目以内に入りませんので、議席を獲得できません。

今、4つの政党が10の議席を争う選挙が行なわれ、それぞれ5100票、2200票、900票、および500票を得たとします。 ドント方式に基づいて、政党ごとの議席数を計算してください。

b00a001@Ampere:~/java% java PartyTest
5100 votes, 6 seats.
2200 votes, 3 seats.
900 votes, 1 seats.
500 votes, 0 seats.
b00a001@Ampere:~/java%

プログラムは穴埋めとします。 コメントを参考にして、???の部分を正しいものにしてください。 ポイントは、すべての商を一気に計算するのではなく、必要な部分だけを計算するところです。 その部分とは、政党ごとの商

得票数 / (現在の議席数 + 1)

です。 この商が最大となる政党を選び、その政党の議席数を一つ増やすという手続きを、全議席数の数だけ繰り返せばよいのです。

Party.java
class Party {
    ??? // 政党の得票数と議席数を格納するフィールド (例
    ??? // えば vote と seat) の定義。

    ??? // 全議席数 10 を定数として TOTALSEATS に格納。

    Party (int vote) {
        ??? // コンストラクタの定義。その政党の得票数は
        ??? // 引数の vote、議席数は 0。
    }
    void add () {
        ??? // その政党の議席数を一つ増やす。
    }
    void print () {
        ??? // その政党の得票数と議席数を画面に出力する。
    }
    static Party maxParty (Party[] p) {
        ??? // 商「得票数 / (現在の議席数 + 1)」が最大
        ??? // である政党を返す。引数 p は政党の配列。
        ??? // はじめに p[0] を候補としてその添字と商
        ??? // (例えば index と quotient) を覚えておき、
        ??? // p[1], p[2], ... の商と比較して、quotient
        ??? // より大きければ index と quotient を更新
        ??? // すればよい。
    }
}
PartyTest.java
class PartyTest {
    public static void main (String[] args) {
        int i;
        Party[] p = {new Party(5100), new Party(2200),
                     new Party(900), new Party(500)};
        for (i = 0; i < Party.TOTALSEATS; i++) {
            Party.maxParty(p).add();
        }
        for (i = 0; i < p.length; i++) {
            p[i].print();
            System.out.println();
        }
    }
}

10.3 レポート課題

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


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

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