プログラミング入門(金曜1・2限)講義資料(2008年後期)

プログラミング入門 第8回

前回の復習

クラスを作る

オブジェクト指向プログラミングでは,様々なオブジェクトを作って,そのメソッドを呼び出すことによってプログラムを動かします.

しかし,都合が良いクラスがいつもあるわけではありません.このため,Javaではクラスを自分で作れるという強力な機能が備わっています.自分でクラスを作ることができると,複雑なプログラムを(部品に分けることで)容易に書くことができるようになります.今日はクラスを作る方法を学びましょう.

例題

何人かで遊べるすごろくゲームをつくる場合を考えます.すごろくゲームは,すごろく盤,プレーヤの駒,サイコロ等の部品で構成されています.これらの部品は,クラス化する候補になります.

ここでは,サイコロを表すDiceクラスを作ってみます.

すごろくゲームを作る時に,サイコロを振る処理を,「1〜6 までの乱数を発生させる」ように書くよりも,Diceクラスのオブジェクトに対して「振れ」と命令できたほうがわかりやすいプログラムになります.

だと思いませんか?

クラスを作るときは,作るものに対してどのような操作ができるのか,またそのものがどのような状態をもっているのかを考える必要があります.操作はメソッドに対応し,状態はインスタンス変数に対応します.

サイコロに対してどのような操作ができるでしょうか,またサイコロはどのような状態を持っているでしょうか?

操作: 状態:

これをクラスにしてみます.

メソッド

あるサイコロに対して行う操作は,インスタンスメソッドになります.

APIドキュメント風にメソッドを書いてみます.

メソッド:

返り値の型メソッド名機能
voidroll()サイコロを振ります
intgetNumber()サイコロの目を得ます
voidsetNumber(int n)サイコロの目を n に設定します(イカサマに便利?)

voidは返り値がないという意味です.

インスタンス変数

インスタンス変数は,オブジェクトが存在する限り覚えておく必要があるデータを入れておくために使います.サイコロの場合,サイコロが振られたら,後でサイコロの目を見る時まで,その目を覚えておく必要があるので,サイコロの目はインスタンス変数としておくのが良いわけです.

(逆に言うと,一時的にしか必要ない変数は,インスタンス変数にするべきではありません.)

インスタンス変数は,オブジェクトがヒープ領域に存在している限り,オブジェクトの中に存在し続けます.

Diceクラスのインスタンス変数としては,サイコロの目の値(1〜6)を int 型の変数 num で持つことにしましょう.これで1つのサイコロの状態を表現できます.

コンストラクタ

次に,サイコロを作り出すとき(Dice オブジェクトが new によって作られたとき),サイコロをどのような状態にしておくと便利かどうかを考えます.とりあえず,今回は乱数を使って目を1〜6の値に設定しておくことにしましょう.

コンストラクタ:

Dice()サイコロの目を乱数で 1〜6 の値に設定する

(なお,コンストラクタは必要なければ(つまりインスタンス変数を初期化する必要がなければ)作成する必要はありません.)

公開と非公開

Dice クラスの利用者がインスタンス変数(num)にむやみにアクセスできると危険です(例えば,サイコロの目を100に書き換えることができます).このため,これはクラスの中に隠しておきます.隠してしまうと,クラスの利用者からはそのインスタンス変数にアクセスすることは出来ません(情報隠蔽).

Javaでは,インスタンスメソッド,クラスメソッド,インスタンス変数,クラス変数のそれぞれを,公開するか非公開にするかを指定することができます.非公開のものは,クラスの内部で(こっそり)使うことしかできません.

公開するには public, 非公開にするには private と書きます.クラスの利用者は,公開されているメソッド,変数にだけアクセスできます.APIドキュメントには公開されているものしか書いてありません.

非公開にすることで,それがクラスの利用者に決して使われないことを保証できます.この例ではnumを非公開にしてあるので,将来 Dice クラスを修正してサイコロの目の表現方法をint型以外に変更しても(あまりありそうにありませんが),クラスの利用者には影響を与えないことが保証されます.

Dice.javaの例

Javaでは,XXX クラスは XXX.java というファイル名のファイルに格納する必要があります.ここでは Dice クラスを作っているので Dice.java というファイルになります.


このファイルをダウンロード ■ UNIX用(EUC版) ■ Windows用(SJIS版)
(上のどちらかのリンクを右ボタンでクリックして「リンク先を名前をつけて保存」して下さい)

// Dice クラスの定義
// Dice.java というファイル名でセーブすること
public class Dice {
    // ■ インスタンス変数の定義 ■
    // インスタンス変数はオブジェクト毎に存在する
    // 現在のサイコロの目を表す変数.非公開.
    private int num;

    // ■ コンストラクタの定義 ■
    public Dice() {
	// メソッドを呼び出すこともできる.
	// this に注意
	this.roll();
    }

    // ■ メソッドの定義 ■

    // サイコロを振る (公開メソッド)
    // 値は返さない (void)
    public void roll() {
	num = (int)(Math.random() * 6) + 1;
    }

    // サイコロの表示している目を得る.
    // int 型の値を返す
    public int getNumber() {
	// 値を返すには return 文を使う.
	return num;
    }

    // サイコロを特定の目にセットする
    public void setNumber(int n) {
	num = n;
    }
}


演習(クラスの練習)

Diceクラスを使って,次のような簡単なプログラム DiceTest を作ってください.

基本的には DiceTest.java,Dice.java 両方をコンパイルする必要があります.

Diceオブジェクトを2つ作った場合,インスタンス変数numも2つ作られます.

クラス定義の方法の解説

クラス

1つのクラスは,クラス名.java という1つのファイルに書きます.

public class クラス名 {
    クラスの中身
}

インスタンス変数の定義

インスタンス変数は,クラスの中身のところに書きます.

public class クラス名 {
    public 変数の型 変数名;  // 公開するインスタンス変数
    private 変数の型 変数名; // 非公開のインスタンス変数
    ...
}

publicと書くとクラスの利用者が無制限にアクセスできます.privateと書くとクラス内部からのみアクセス可能です.

publicなインスタンス変数は(危険なので)あまり使いません.この講義では,インスタンス変数はすべて private にして下さい.

クラス変数の定義

private static int num; のようにstaticをつけると,インスタンス変数ではなくクラス変数になります.

クラス変数はクラスに対して1つしか作られないので,ヒープ領域にはありません.

インスタンスメソッドの定義

インスタンスメソッドは,必ずオブジェクトと結び付いて呼び出されます.(→ あるサイコロを 転がせ.).

  Dice dice = new Dice();
  dice.roll();

インスタンスメソッドでは,そのオブジェクトの中身を操作することができます.(基本的に他のオブジェクトの中身を操作することはできません.)

インスタンスメソッドを定義するには,次のように書きます.

public class クラス名 {
    public 返り値の型 メソッド名 (引数の列) {
        メソッドの処理
    }
    ...
}

引数がない場合は,メソッド名の後に () と書きます.

返り値がない場合は,void と書きます.

インスタンスメソッドの中では,以下のようなことができます.

メソッドから値を返すためにはreturn文を使います.return の後に書いた値がメソッドから返されます.return 文を実行すると,プログラムはそのメソッドから直ちに脱出して,呼び出し元に戻ります.

返り値がvoidでないメソッドには,必ず return 文が必要です(return文がないとコンパイルエラーになります.)

慣例としてメソッド名は小文字から始め,最初の文字は動詞にします.動詞の後にさらに単語が続く場合,単語の最初の文字を大文字にします.getNumber, setNumberのように.

同じ名前のメソッドを複数作ることができます.その場合,引数のパターンが異なっている必要があります.このようなメソッドを呼び出すときは,コンパイラが引数のパターンからどのメソッドを呼び出せば良いのかを自動的に選択します.

クラスメソッドの定義

staticをつけると,クラスメソッドになります.クラスメソッド内では,インスタンス変数にはアクセスできません(オブジェクトが存在しなくても呼び出せるのがクラスメソッドなので)

実は,mainメソッドは,Javaのプログラムを実行するときに最初に実行されるクラスメソッドなのです.

コンストラクタ

コンストラクタは必ず public です.メソッドの定義と似ていますが,返り値はありません.必ずクラス名をつけることになっています.

public class クラス名 {
    public クラス名(引数の列) {
        コンストラクタの処理
    }
}

メソッドと同じように,引数が異なっていれば,同じ名前のコンストラクタを複数作ることができます.

this

メソッドの内部では,操作しているオブジェクトは this という変数から参照することができます.this は,常にそのメソッドが呼び出された元のオブジェクトを指している特別な変数です.

メソッド内で this.roll() のように書くと,別のメソッドを呼び出すことができます.ただし,roll() と書くだけで,暗黙にthis.roll() の意味になります.

コンストラクタでも this を使うことができます.

演習

DiceクラスのコンストラクタにSystem.out.printlnを入れて,new によってコンストラクタが呼び出されていることを確認してください.

DiceTestから,privateなインスタンス変数(num)にアクセスしてみて,コンパイルエラーとなることを確かめてください.

弁当屋さん経営ゲーム

自分でクラスを定義すると,より複雑なプログラムを書くことができるようになります.ここでは,弁当屋さんを経営するゲーム BentoGame を書いてみましょう.

ルール

あなたは仕出し弁当屋さんの経営者です.

弁当の原価は1つ400円,販売価格は500円です.1日に売れる個数は天気に左右されます.雨の日は(外に出たがらないので)500個,曇りの日は300個,晴れの日は100個まで売れます.次の日の天気予報を見て,何個作るか考えないといけません.弁当は日持ちしないので,余った分は全て処分されます.

最初の所持金は40,000円です.

あなたの店の目の前にはコンピュータが経営する別の店があります.早く所持金が100,000円になったほうが勝ちです.

実行画面

実行画面はこんな感じです.

% java BentoGame
あなたの資金: 40000円
コンピュータの資金: 40000円
天気予報: 明日の降水確率は 42%
いくつ弁当を作りますか?(100個まで): 80
あなたの店は80個作ります
コンピュータの店は100個作ります
天気は...晴れでした
あなたの店の弁当は80個売れました
コンピュータの店の弁当は100個売れました

あなたの資金: 48000円
コンピュータの資金: 50000円
天気予報: 明日の降水確率は 93%
いくつ弁当を作りますか?(120個まで): 

クラスに分ける

まず,どのようなクラスを作れば良いのかを考えます.これは経験を要する部分ですが,基本的に登場する名詞がクラスになる候補になると考えると良いでしょう.また,動詞はメソッドの候補になります.

このゲームの場合,あなたの店,弁当,天気(天気予報),所持金,コンピュータの店,がクラスにする候補になるでしょう.このうち,何らかの機能をもっているものをクラスにすると良いようです.

そこで,「あなたの店」を Player,「コンピュータの店」を CompPlayer,「天気」を Weather というクラスに対応させることにします.

Weatherクラス

Weatherクラスは,乱数によって降水確率を計算します.またそれに応じた実際の天気(晴れ,曇り,雨)も計算します.

メソッド:

voidmakeWeather()降水確率と天気を計算する.
intgetKousuiKakuritsu()計算した降水確率を返す.
intgetTenki()計算した天気を返す(0 = 晴れ,1 = 曇り, 2 = 雨)
StringgetTenkiString()計算した天気を文字列で返す("晴れ", "曇り", "雨")

インスタンス変数:

private intkousui降水確率
private inttenki天気

Playerクラス, CompPlayerクラス

Player, CompPlayerは,ほとんど同じ機能を持っています.

メソッド:

voidprepareBento(int kousui)弁当の準備をする.
  • Playerの場合,降水確率を表示する.
  • いくつ弁当を作るか決める(Playerの場合はキーボードから入力して貰う,CompPlayerの場合は適当な戦略で計算して決める).
  • 作ると決めた数を zaiko に入れる.
  • money を原価 * zaiko だけ減らす.

intsellBento(int tenki)弁当の在庫を売る.
  • 最大幾つ売れるかは天気次第なので,引数の tenki (0 = 晴れ,1 = 曇り, 2 = 雨)で決定する.
  • money を売れた分だけ増やす.
  • 弁当が売れた数を返す.
intgetMoney()所持金を返す.

インスタンス変数:

private intmoney所持金
private intzaiko弁当の在庫

最初の所持金は 40000 円ですが,最初にインスタンス変数 money を 40000 に入れるには,コンストラクタ内で代入するか,あるいはインスタンス変数を作るときに private int money = 40000; と書きます.

最初は簡単なものから

最初はルールが簡単な SimpleBentoGame を作りましょう.コンピュータの店は存在しないことにします.また,プログラムは永遠に動き続けるものとします.

Weather.javaは以下のものを使ってください.


このファイルをダウンロード ■ UNIX用(EUC版) ■ Windows用(SJIS版)
(上のどちらかのリンクを右ボタンでクリックして「リンク先を名前をつけて保存」して下さい)

// Weather.java
// 天気を表すクラス
public class Weather {
    // 降水確率
    private int kousui;
    // 天気 0 = 晴, 1 = 曇, 2 = 雨
    private int tenki; 
    // 天気を表す文字列
    // これはオブジェクト毎に異なる必要はないので
    // クラス変数にしている
    static private String[] tenkimoji = {"晴れ", "曇り", "雨"};

    // 降水確率と天気を計算する
    public void makeWeather() {
	// 乱数で 0〜100 の数を作って,降水確率とする
	kousui = (int)(Math.random() * 101);

	// 天気は,別の乱数 r で決める.
	// r < 降水確率 - 10 なら雨,r < 降水確率 + 10 なら曇り,それ以外は晴れ
	// (手抜き!)
	int r = (int)(Math.random() * 101);
	if (r < kousui - 10) {
	    tenki = 2;	// 雨
	} else if (r < kousui + 10) {
	    tenki = 1;	// 曇り
	} else {
	    tenki = 0;	// 晴れ
	}
    }

    // 降水確率を返す
    public int getKousuiKakuritsu() {
	return kousui;
    }

    // 計算した天気を返す
    public int getTenki() {
	return tenki;
    }

    // 計算した天気を文字列で返す
    public String getTenkiString() {
	return tenkimoji[tenki];
    }
}


SimpleBentoGame.java は以下のものを使ってください.


このファイルをダウンロード ■ UNIX用(EUC版) ■ Windows用(SJIS版)
(上のどちらかのリンクを右ボタンでクリックして「リンク先を名前をつけて保存」して下さい)

// SimpleBentoGame.java
// コンピュータの店がない簡易版弁当屋さん経営ゲーム
public class SimpleBentoGame {
    public static void main(String[] args) {
	Player player = new Player();
	Weather w = new Weather();

	while (true) {
	    w.makeWeather();

	    System.out.println("あなたの資金: " + player.getMoney() + "円");

	    player.prepareBento(w.getKousuiKakuritsu());

	    System.out.println("天気は..." + w.getTenkiString() + "でした");

	    int sold = player.sellBento(w.getTenki());

	    System.out.println("あなたの店の弁当は" + sold + "個売れました");
	    System.out.println();
	}
    }
}


課題8-1 (簡易版弁当屋さん経営ゲーム)

Player.javaを書いて,簡易版ゲームを動かしてください.

prepareBentoメソッドでは,所持金で作れる最大の弁当の数を越えるような数を受け付けないようにチェックして,無効な数だった場合は再度入力するようにしましょう.

1. Player.java,2. 実行結果(途中までで良い),3. 苦労したところや改善すべき点,感想 を書くこと.