Javaプログラミング基礎

講義資料

オブジェクト指向プログラミング(1) - クラスを用いたプログラム

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

Java をはじめとする 「オブジェクト指向 (Object Oriented) 」のプログラミング言語では、 プログラムの中に現れる対象物 (例えば、データのまとまり、ボール、ある人のプロフィール) に関するデータや、関連性の強い複数のメソッドをひとまとめにする仕組みがあります。 「対象物」をひとまとめにしてプログラムを書くことにより、 大規模なシステムをわかりやすく開発することができます。 この「対象物」に関するプログラムのかたまりを「クラス (class) 」と呼びます。

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

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

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

住所録を管理する簡単なプログラムについて考えてみます。 このプログラムは、名前と住所の 2 つの情報を扱うものです。 (ファイル名: AddressNoteNotOO.java)

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: 関連する複数の変数をまとめる

クラスを使うと、 複数の変数からなるプログラムの中のデータ処理や計算の対象を 一つにまとめておくことができます。 (ファイル名: AddressNoteWeakOO.java)

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

	medio.address = "東京都千代田区神田錦町2-2";
	medio.name = "電大 メディ男";

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

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

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

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

$ java AddressNoteWeakOO

と入力します。 プログラムはコマンド java に続けて指定された クラス AddressNoteWeakOOpublic static void main の部分から実行が始まります。

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

クラスの中で宣言されている変数を、 「(そのクラスの) 属性 (attribute) 」、 または「フィールド (field)」、 「インスタンス変数 (instance variable) 」 などと呼びます。 属性は、これまでの変数宣言と同様の形式で書くことができます。 (他の言語 (C++ など) では属性のことをメンバ変数と呼ぶこともあります。)

これにより main メソッドでは、 次のようにしてクラス Person のデータを使うことができます。

Person medio;

まず上の変数宣言では、クラス Person のデータを 変数 medio で使うことができるようにしています。 このようにクラスは、 int や double のような型の一種として使うことができます。

次に進みましょう。

medio = new Person();

new は指定されたクラスから、 実際にデータを入れたり処理を行ったりできる領域を 新たに作り出すための演算子です。 ここで作られた領域を 「(そのクラスの) インスタンス (instance)」、 または「オブジェクト (object)」 と呼びます。

new 演算子のあとに、 インスタンスを生成したいクラス名と続けてカッコを書きます。 カッコの中にはインスタンスの初期化のための情報を書くこともできます。 これ以降 medio という変数によってクラス Person の インスタンスを使うことができます。

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

次に進みましょう。

medio.address = "東京都千代田区神田錦町2-2";
medio.name = "電大 メディ男";
printAddress(medio.address);
printName(medio.name);

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

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

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

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

medio.address = "東京都千代田区神田錦町2-2";
medio.name = "電大 メディ男";
printAddress(medio.address);
printName(medio.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;
    }
}
    
class AddressNote {
    public static void main(String[] args) {
	Person medio = new Person();

	medio.setAddress("東京都千代田区神田錦町2-2");
	medio.setName("電大 メディ男");

	medio.printAddress();
	medio.printName();
    }
}

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

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

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

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

さて、 以前は変数 addressname への代入で medio の属性に値を登録していた部分を、 メソッド 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;
    }
}
    
class AddressNote2 {
    public static void main(String[] args) {
	Person medio = new Person();
	Person denko = new Person();

	medio.setAddress("東京都千代田区内神田1-14-8");
	medio.setName("電大 メディ男");
	denko.setAddress("東京都千代田区神田錦町2-2");
	denko.setName("電大 デン子");

	medio.printAddress();
	medio.printName();
	denko.printAddress();
	denko.printName();
    }
}

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

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

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

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

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

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

class クラス名 {

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


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

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

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

int a;

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

Person dendaiMedio = new Person();

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

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

Person dendaiMedio = new Person();

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

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

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

Person dendaiMedio = new Person();
Person dendaiDenko = new Person();

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

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

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 (サボテン) の内容:

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

        // 最初は高さ 100mm = 10cm 、成長率年間 5mm とする
        myCactus.setHeightAndGroth(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 groth;

    void setHeightAndGroth(int x, int y) {
        height = x;
        groth = y;
    }

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

    int getHeight() {
        return height;
    }
}