Javaプログラミング基礎 講義資料

クラスを用いたプログラム

前回はプログラムで行う仕事を、 メソッドと呼ばれる処理単位にまとめておき、 必要なときにメソッドを実行する方法を学びました。 実は、規模の大きなプログラムや複雑な手順の処理を行うプログラムでは、 メソッドによる機能の分割だけでは不十分なのです。

Java をはじめとする 「オブジェクト指向 (object oriented) 」のプログラミング言語では、 プログラムの中に現れる対象物 (例えば、ある人のプロフィール、ゲームのキャラクタ、アニメーションするボールなど) に関する処理をひとまとめにする仕組みがあります。 「対象物」をひとまとめにしてプログラムを書くことにより、 わかりやすいプログラムを書くことができます。 この「対象物」に関するプログラムのかたまりを「クラス (class) 」と呼びます。

クラスを用いたプログラム

今回は、オブジェクト指向プログラミング言語の機能のうち 「クラス」について学びます。 まず、これまで学んだクラスを使わずにメソッドのみを使った プログラムの例を示しながら、 プログラムの問題点とクラスの概念を導入するメリットを説明していきます。

例題1: オブジェクト指向以前のプログラム

住所録を管理する簡単なプログラムについて考えてみます。 このプログラムは、ある人物の名前と住所の 2 つの情報を扱うものです。 まず、これまで学んだ、変数とメソッドを用いたプログラムを書いてみましょう。 (ファイル名: AddressNoteNotOO.java)

public class AddressNoteNotOO {
    public static void main(String[] args) {
	String address;
	String name;

	address = "東京都千代田区神田錦町2-2";
	name = "電大 未来男";

	printAddress(address);
	printName(name);
    }

    static void printAddress(String address) {
	System.out.println("住所: " + address);
    }

    static void printName(String name) {
	System.out.println("名前: " + name);
    }
}

main メソッドの 2 つの変数 addressname が、 住所と名前の情報を入れておくための変数であり、 printAddressprintName が それを表示するためのメソッドです。

例題程度の規模のプログラムではそれほど問題になりませんが、 大規模なプログラムでは、 このプログラムの方法では次のことが問題になると予想されます。

例題2: 関連する複数の変数をまとめる

この例題以降、 このプログラムで登場する「一人の人物」にひたすら注目していくことになります。 「このプログラムで登場する人物とはどんなデータなのか」という構造を、 プログラムで表現することを考えてみます。

Javaでは、「一人の人物」を「クラス」というグループにして独立させ、 その人物に関する住所と名前などの情報をうまくまとめて書くことができます。

以下のプログラムを見てみましょう。 (ファイル名: AddressNoteWeakOO.java)

class Person {
    String address;
    String name;
}
    
public class AddressNoteWeakOO {
    public static void main(String[] args) {
	Person mikio;
	mikio = new Person();

	mikio.address = "東京都千代田区神田錦町2-2";
	mikio.name = "電大 未来男";

	printAddress(mikio.address);
	printName(mikio.name);
    }

    static void printAddress(String address) {
	System.out.println("住所: " + address);
    }

    static void printName(String name) {
	System.out.println("名前: " + name);
    }
}

このプログラムの実行するには、コマンドプロンプトから、

$ java AddressNoteWeakOO

と入力します。

プログラムの最初に新たに追加した class Person { 〜 } が、 一人の住所データをひとまとめにして扱うためのクラス定義です。 この例題では、addressname の 2 つの変数からなる ひとまとまりのデータに Person というクラス名をつけています。

一人の人物についての、住所 (address) や名前 (name) などの中身の情報のことを、 Java では「属性 (attribute) 」または、「フィールド (field)」または、 「インスタンス変数 (instance variable) 」 などと呼び、変数の一種とみなせます。

(他の言語 (C++ など) では属性のことをメンバ変数と呼ぶこともあります。)

一人の人物 (Person) がクラスとして定義できると main メソッドでは、 次のようにして人物のデータを使うことができます。

Person mikio;

人物のデータを入れることができる変数 mikio を変数宣言しています。 これまで「整数を扱うための変数 a を用意する」ために、 int a のように変数宣言しましたね。 人物についても同様に「人物 (Person) を扱うための変数 mikio を用意した」 のです。 このようにクラスは、 int や double のような型の一種として使うことができます。

次に進みましょう。

mikio = new Person();

new は指定されたクラスから、 実際にデータを入れたり処理を行ったりできる領域を 新たに作り出すための決まり文句です。

なお、上の 2 行を 1 行にまとめて Person mikio = new Person(); のように書くこともできます。

こうしてできた変数 mikio には、 実際の人物に関する情報を入れることができるようになります。 この変数のことを「オブジェクト (object)」または「インスタンス (instance)」と 言います。

次に進みましょう。

mikio.address = "東京都千代田区神田錦町2-2";
mikio.name = "電大 未来男";
printAddress(mikio.address);
printName(mikio.name);

インスタンスの属性を使うためには、 インスタンスの変数名に続けてピリオド (.) を書き、 属性名を書きます。属性は通常の変数と同じ様に使うことができます。

今回のクラスを作るのにあたり注目したのは、 一人の住所データを一つのクラスで表そうということです。 このように、プログラムの中の処理の対象物をクラスとし、 対象物の複数のデータを属性としてまとめておくことにより、 プログラムの中でのデータの構造をわかりやすく表すことができます。

例題3: データとそのデータに関する処理をまとめる

mikio に対する情報の出入りについて考えてみましょう。 前の例題では以下のような mikio に対するデータの操作を行っていました。 2 つの代入は、mikio に対して各属性の値を登録する処理、 次の 2 つのメソッドの実行は各属性の値を表示する処理です。

mikio.address = "東京都千代田区神田錦町2-2";
mikio.name = "電大 未来男";
printAddress(mikio.address);
printName(mikio.name);

これらの処理はクラス Person の内部データに関する処理であることから、 直接 main メソッドに書くのではなく クラス Person の定義の中に併せて書くことができます。 すなわち、属性の値を登録するメソッド (setAddress, setName) と、 属性の値を表示するメソッド (printAddress, printName) をクラス Person の定義の中にまとめておけば良いのです。 (ファイル名: AddressNote.java)

class Person {
    String address;
    String name;

    void printAddress() {
	System.out.println("住所: " + address);
    }

    void printName() {
	System.out.println("名前: " + name);
    }

    void setAddress(String a) {
	address = a;
    }

    void setName(String n) {
	name = n;
    }
}
    
public class AddressNote {
    public static void main(String[] args) {
	Person mikio = new Person();

	mikio.setAddress("東京都千代田区神田錦町2-2");
	mikio.setName("電大 未来男");

	mikio.printAddress();
	mikio.printName();
    }
}

前の例題と良く見比べてください。

クラス Person に新たに追加したのは、 メソッド setAddress, setName, printAddress, printName です。 前の例題では、クラス Person に関するデータ処理が、 main メソッドに存在していたわけですが、 これをクラス Person の中に持ってきたというわけです。 これで、データとそのデータに対する処理を クラス Person の中に整理することができました。

一方 main メソッドでは、 メソッド setAddress, setName, printAddress, printName を実行しているだけです。 インスタンスに対してメソッドを実行するためには、 属性へのアクセスと同様に、インスタンスの変数名に続けてピリオド (.) を書き、 メソッド名を書きます。 ここでは、 インスタンスである mikio に対しての 操作を行うメソッドを実行しているというわけです。

このようなメソッドの実行形態は、 あるプログラムの中での処理対象 (インスタンス) に対して指令 (メッセージ) を送っていると考えると良いでしょう。 例えば、インスタンス mikio に対し、 「名前を登録せよ (setName) 」や、 「住所を表示せよ (printAddress) 」 というメッセージを送っているという具合いです。

さて、 以前は変数 addressname への代入で mikio の属性に値を登録していた部分を、 メソッド setAddress, setName の実行に書き換えました。 このように全てメソッドを介してインスタンスへのアクセスをすることで、 クラス Person の内部状態に依らず目的の仕事を行うことができます。 つまり、クラスを使う側から見ると、 メソッドの実行方法だけを知っていれば、 あとはクラスが所定の仕事をやってくれると信頼して任せてしまえば良いのです。 クラスの内部状態やデータの構造などは関知する必要はありません。

一方クラス内では、クラス内の属性へアクセスが 必ずメソッドを介して行うようにすれば、 メソッドの中で値のチェックをしたり、 アクセス制限を行うといった属性の値の管理が可能になります。

ところで、メソッドを Person の中に書く際に、 キーワード static をつけませんでした。 インスタンスを生成してから利用するメソッドは、 static をつけて宣言してはいけません。

この例題を通して、 オブジェクト指向プログラミングにおけるクラスを用いた プログラムの考え方が理解できたはずです。 これらをまとめると次のようになります。

例題4: 複数のデータを簡単に扱う

最初に示した例題のプログラムでは、 複数の人の住所データを扱うのは難しいという問題点がありました。

クラスは「型」の一種であり、 クラスからインスタンスを生成しましたね。 int 型の変数がいくつでも宣言できるのと同様に、 一つのクラスからいくつでもインスタンスを生成し利用することができるのです。 (ファイル名: AddressNote2.java)

class Person {
    String address;
    String name;

    void printAddress() {
	System.out.println("住所: " + address);
    }

    void printName() {
	System.out.println("名前: " + name);
    }

    void setAddress(String a) {
	address = a;
    }

    void setName(String n) {
	name = n;
    }
}
    
public class AddressNote2 {
    public static void main(String[] args) {
	Person mikio = new Person();
	Person kimiko = new Person();

	mikio.setAddress("東京都千代田区内神田1-14-8");
	mikio.setName("電大 未来男");
	kimiko.setAddress("東京都千代田区神田錦町2-2");
	kimiko.setName("電大 来未子");

	mikio.printAddress();
	mikio.printName();
	kimiko.printAddress();
	kimiko.printName();
    }
}

このプログラムの前半部分、クラス Person の定義は前の例題と同様です。

main メソッドでは、mikiokimiko の 2 つのインスタンスを宣言・生成しています。 一旦インスタンスを生成すると、 それぞれのインスタンスは独立したデータとして振る舞います。 すなわち mikio の属性やメソッドが kimiko のデータに影響を与えることや、その逆はないのです。

このように、プログラムの中に登場する処理の対象の性質を一旦クラスとして定義してしまえば、 具体的な対象物ごとにインスタンスをいくつでも生成できるのです。

クラスを用いたプログラムの文法

実は、今まで class 〜 で始めていたプログラム自身もクラスのひとつなのです。 一般に、 Java のプログラムはいくつかのクラス定義を並べた形をしています。

クラス定義の文法は次のとおりです。

class クラス名 {

    int member;                      ←属性とする変数の宣言
    ...                                (属性のないクラスの場合省略可)


    int method(int arg, ... ) {      ←メソッドの宣言
        ...                            (メソッドのないクラスの場合省略可)
    }
    ...
}

まず、プログラムの中で登場する対象物を分析し「これをクラスにしよう」と決めます。 次に、対象物の中にあるデータを属性として変数宣言します。 そして、その変数に対してどんな計算を行うのか、 このクラスはどんな情報を出し入れするのかという処理の内容をメソッドとして宣言します。

プログラムの中では、クラスは型の一種として使うことができます。 型とは int や String のような、変数に入れておくことのできる値の種類を示すものでした。 例えば、

int a;

上のプログラムは、整数を入れることができる (int型) 変数 a を宣言するという意味でした。 ここで、住所録の個人データを入れることができる Person クラスがあったとすると、 このクラスは実際の個人データを入れる変数を宣言して使うことになります。

Person mikio = new Person();

new Person() は、クラスの内容を初期化し 実際にデータを入れることができる準備をするための書き方です。

変数 mikio に対して住所録情報を登録したり処理したりすることができます。 この、実際にデータを扱う対象のことをオブジェクトまたはインスタンスと言います。

Person mikio = new Person();

mikio に対して情報を登録、表示など
...
...

このように、クラスを定義した後、オブジェクトを生成することで、 実際にデータを入れたり処理を行うことができます。 クラスからオブジェクトと生成することを インスタンス化すると言います。

クラスから、いくつでもオブジェクトを生成することができます。 住所録の例では一度 Person クラスを定義してしまえば、 オブジェクトを生成することによって何人ぶんの住所録でも作ることができます。

Person mikio  = new Person();
Person kimiko = new Person();

例題: クラスを用いた電卓プログラム

四則演算を行う電卓機能を1つのクラス(クラス Calculator)としてみましょう。 この電卓に一旦値を記憶するメモリ機能があるとします。 ファイル名は CalcOO.java とします。

public class CalcOO {
    public static void main(String[] args) {
        Calculator calc = new Caluculator();

        System.out.println("1 + 2 = " +  calc.add(1, 2) );
        System.out.println("5 - 4 = " +  calc.sub(5, 4) );

        // 5 * (3 - 2) を計算

        // まず 3 - 2 を計算しメモリに記憶
	calc.setMemory(calc.sub(3, 2));
        // メモリから値を取り出し 5 と掛け算
	int ans = calc.multi(5, calc.getMemory());

        Sysytem.out.println("5 * (3 - 2) = " + ans);
    }
}

class Calculator {
    int memory;

    void setMemory(int x) {
        memory = x;
    }

    int getMemory() {
        return memory;
    }

    int add(int x, int y) {
        int ans;
        ans = x + y;
        return ans;
    }

    int sub(int x, int y) {
        int ans;
        ans = x - y;
        return ans;
    }

    int multi(int x, int y) {
        int ans;
        ans = x * y;
        return ans;
    }

    int divide(int x. int y) {
        int ans;
        ans = x / y;
        return ans;
    }
}

例題: 成長するサボテン

1年に一定の長さで成長するサボテンの高さを計算し表示するプログラムです。 このプログラムではサボテンをクラスとします。 (ファイル名 CactusObservation.java)

クラス Cactus (サボテン) の内容:

public class CactusObservation {
    public static void main(String[] args) {
        Cactus myCactus = new Cactus();

        // 最初は高さ 100mm = 10cm 、成長率年間 5mm とする
        myCactus.setHeightAndGrowth(100, 5);

	System.out.println("現在のサボテンの背丈は " + myCactus.getHeight());

        // 1年後の経過をみる
        myCactus.grown(1);
	System.out.println("現在のサボテンの背丈は " + myCactus.getHeight());

        // さらにそれから2年後の経過をみる
        myCactus.grown(2);
	System.out.println("現在のサボテンの背丈は " + myCactus.getHeight());
    }
}

class Cactus {
    int height;
    int growth;

    void setHeightAndGrowth(int x, int y) {
        height = x;
        growth = y;
    }

    void grown(int years) {
        height = height + growth * years;
    }

    int getHeight() {
        return height;
    }
}