クラスとは

「第3章」の「Javaプログラムの構成」でお話ししたように、Javaのプログラムは1つ以上のクラスから成り立ちます。 ですから、これまでも多くのクラスを記述してきたわけですが、実はクラスについてまだほとんど何も学習していないのです。 ここからは、いよいよクラスについて本格的に学習していきましょう。

データの表し方

データとは、人や物に関する情報を数値や文字等で表したものです。 そして、このデータを処理することがプログラムの使命なのです。

これまでは非常に単純なデータばかりを扱ってきました。 整数を表すにはint型、文字列ならString型を使えばよかったですね。 ところが、現実のプログラムでは、これらの型を利用するだけでは扱いにくいデータが数多く登場します。 試しに、プログラム上でロボットを表現する方法を考えてみましょう。 それぞれのロボットには、名前とバッテリー残量をデータとして持たせます。

RobotsSample.java
class RobotsSample {
    public static void main(String[] args) {
        String robot1Name = "ロボ太郎";
        int robot1Battery = 100;

        String robot2Name = "ロボ子";
        int robot2Battery = 90;

        System.out.println("名前: " + robot1Name);
        System.out.println("バッテリー残量: " + robot1Battery);
        System.out.println();
        System.out.println("名前: " + robot2Name);
        System.out.println("バッテリー残量: " + robot2Battery);
    }
}
RobotsSampleの実行結果
名前: ロボ太郎
バッテリー残量: 100

名前: ロボ子
バッテリー残量: 90

実行すると、2体のロボットの名前とバッテリー残量が表示されましたね。 このプログラムには何ら文法的な間違いは含まれていません。 しかし、問題点が有ります。 次の部分に注目してください。

        String robot1Name = "ロボ太郎";
        int robot1Battery = 100;

ロボットは名前とバッテリー残量というデータを持っていて、それぞれString型、int型の変数に代入されています。 このような設計では、新しいロボットを作るたびに、これら2つの変数をいちいち用意してやらなければいけません。 その際、すべてのロボットに統一性も持たせるため、変数名のスペルや変数の型を間違わないように注意しなければならず、あまり能率的とは言えません。 ロボットが名前とバッテリー残量以外にもデータを持つならば、さらに面倒なことになるでしょう。

もっとスマートなやり方は無いでしょうか。 ロボットが名前とバッテリー残量というデータを持つことが決まっているならば、そのようにロボットの構造をプログラム上で定義しておけばよいのではないでしょうか。 つまり、この2つのデータをまとめて、ロボットという新しい型を自分で作ってしまうのです。 Javaではこの新しい型をクラス(class)として定義することができます。

クラス宣言

ロボットを表すRobotクラスは次のように記述します。 このようにクラスを記述することをクラス宣言(class decrarlation)と言います。

class Robot {
    String name;    //名前
    int battery;    //バッテリー残量
}

クラスの内部に含まれる個々のデータはフィールド(field)で表します。 Robotクラスには、String型のnameというフィールドとint型のbatteryというフィールドが含まれています。 これによって、ロボットは名前とバッテリー残量というデータを持つことが、確実に保証されます。 つまり、新しいロボットを作るたびに、2つの変数を個別に宣言する煩わしさから解放されるのです。 この場合、個々のロボットは構造を持った1つのデータであると言えます。

フィールドは第9章の変数の有効範囲のところで学習しましたね。 ただし、ここではstaticが付かないフィールド、すなわちインスタンス変数だけを扱います。 staticの意味については後ほど学習しましょう。

図 10-1 : データの表し方を比較する

クラスとオブジェクト

クラスを宣言することは、新しい型を定義することでした。 次に、クラスからデータの本体を生成することを考えましょう。 クラスから生成されるデータの本体をインスタンス(instance)と呼びます。 実際にインスタンスを作り、利用するためには、次のステップが必要になります。

  1. クラス型変数を宣言する。
  2. インスタンスを生成して、その結果をクラス型の変数に代入する。

この2つのステップが、配列を利用するときのやり方と似ていることに気づきましたか。

まず、1つ目のステップです。

構文 : クラス型変数の宣言
クラス名 変数名;

例えば、Robot型の変数robot1を宣言するには次のように記述します。

Robot robot1;

配列変数が配列本体を参照する変数であったのと同様に、クラス型変数もインスタンスを参照する変数に過ぎません。 従って、クラス型も参照型に分類されます。

2つ目のステップとして、クラスからインスタンスを生成します。

構文 : インスタンスの生成
変数名 = new クラス名();

クラスからインスタンスを生成することをインスタンス化といいます。 例えば、Robotクラスからインスタンスを生成するには次のように記述します。

robot1 = new Robot();

配列本体をnew演算子で生成して、その結果を配列変数に代入したのと同様に、インスタンスをnew演算子で生成して、その結果をクラス型変数に代入するのです。 その結果、クラス型変数robot1はインスタンスを参照するようになります。

もちろん、2つのステップをまとめて、次のように記述することもできます。

Robot robot1 = new Robot();

図 10-2を見て、クラス型変数の宣言とインスタンスの生成についてよく理解してください。

図 10-2 : クラス型変数の宣言とインスタンスの生成

クラスはデータの一般的な構造を定義するだけであって、個々のデータ本体を表すものではありません。 個々のデータ本体は、プログラムの実行時にnew演算子によってインスタンスとして生成されることになるのです。 このことから、クラスは製品の設計図に、インスタンスは個々の製品に例えられることがあります。

図 10-3 : クラスとインスタンスの関係

配列本体とクラスのインスタンスをまとめて、オブジェクト(object)と呼びます。 オブジェクトとは一般的には物体を意味する言葉であり、プログラミングの世界では、この世のモノ(人、ロボット、車、顧客、商品 etc.)を反映するデータ本体のことを指します。 そして、このオブジェクトを組み合わせてプログラムを作っていくことをオブジェクト指向プログラミング(object oriented programming)と呼ぶのです。

英語のclassは「組」「分類」、instanceは「実例」、objectは「物体」「対象」という意味です。

配列本体とクラスのインスタンスをまとめて、オブジェクトと呼ぶ。

既定値

ところで、図 10-2を見ると、インスタンスの2つのフィールドnameとbatteryは、それぞれnullと0で初期化されています。 配列本体を生成した時点で、各要素が既定値で初期化されたのと同様に、インスタンスの各フィールドも、既定値で初期化されます。 フィールドの型によって既定値は決まっています。 その決まり方は配列の場合と全く同じですが、もう一度表 10-1にまとめておきましょう。

表 10-1 : 既定値
既定値
byte (byte)0
short (short)0
int 0
long 0L
float 0.0f
double 0.0d
char '\u0000'
boolean false
参照型 null

フィールドにアクセスする

生成されたインスタンスは、それぞれがフィールドを持っています。 そのフィールドにアクセスする方法を見ていきましょう。

構文 : フィールドへのアクセス
クラス型変数名.フィールド名

クラス型変数名の後にドット(.)を付け、続けてフィールド名を指定します。 このドット(.)をメンバアクセス演算子(member access operator)と言います。 例えば、robot1が参照するインスタンスは、nameとbatteryという2つのフィールドを持っています。 この場合、nameに"ロボ太郎"という文字列(への参照)を代入し、batteryに100を代入するには、次のように記述します。

        robot1.name = "ロボ太郎";
        robot1.battery = 100;
図 10-4 : フィールドに値を代入する

インスタンス内のフィールドには、メンバアクセス演算子(.)を使って、"クラス型変数名.フィールド名"の形でアクセスする。

Robotクラスを利用するプログラム

では、いよいよRobotクラスを利用してRobotsSampleを書き換えてみましょう。

RobotTest01.java
//ロボットクラス【第1版】
class Robot {
    String name;    //名前
    int battery;    //バッテリー残量
}

//ロボットクラス【第1版】のテスト用クラス
class RobotTest01 {
    public static void main(String[] args) {
        Robot robot1 = new Robot();
        robot1.name = "ロボ太郎";
        robot1.battery = 100;

        Robot robot2 = new Robot();
        robot2.name = "ロボ子";
        robot2.battery = 90;

        System.out.println("名前: " + robot1.name);
        System.out.println("バッテリー残量: " + robot1.battery);
        System.out.println();
        System.out.println("名前: " + robot2.name);
        System.out.println("バッテリー残量: " + robot2.battery);
    }
}

1つのソースコードの中に、2つのクラスが含まれている点がこれまでとは違います。 ソースファイル名はmainメソッドを含んでいるRobotTest01に.javaを付けたものとなっています。 これまでと同様に、ch10フォルダの中にRobotTest01フォルダを作り、その中にRobotTest01.javaを保存します。 コンパイルもこれまで通りのやり方で大丈夫です。 次のように入力してください。

javac RobotTest01.java

この結果、RobotTest01フォルダの中には、Robot.classとRobotTest01.classの2つのクラスファイルが生成されます。

図 10-5 : コンパイルによって生成されるクラスファイル

実行されるのはmainメソッドを含むRobotTest01クラスの方です。 次のように入力してください。

java RobotTest01

実行結果はRobotsSampleと同じです。

RobotTest01の実行結果
名前: ロボ太郎
バッテリー残量: 100

名前: ロボ子
バッテリー残量: 90

Robotクラスでは、フィールドname, batteryがstaticを付けずに宣言されています。 そのため、robot1, robot2が参照するそれぞれのインスタンスの中に、これらのフィールドが含まれることになります。 従って、インスタンス毎にname, batteryの値を代入することができるようになるわけです。 staticを付けずに宣言されたフィールドをインスタンス変数と呼ぶのはそのためです。

図 10-6 : 複数のインスタンスの生成

ひとたびRobotクラスを宣言してしまえば、同じデータ構造を持ったインスタンスを次々に生成することができます。 各インスタンスのフィールドname, batteryに値を代入することもできます。 これで、また一歩スマートなプログラミングができるようになりましたね。

クラスを利用するには、クラスを宣言して、インスタンスを生成する。