クラスの作り方の基本

これまでは、Javaのコアライブラリにあらかじめ用意されているクラスを使うだけでした。しかし、Javaのプログラミングは本来、自分の手で必要なクラスを作り、それを組み上げて1つのソフトウェアを作り上げるものです。分かりやすく使いやすい、良いクラスを作れるようになれば、あなたも一人前のJavaプログラマーです。

今回は、クラスを作るための基本を学びましょう。

空っぽのクラスを作る

Javaプログラミングの基本』の回では、貯金箱のプログラムを、Javaのコアライブラリで用意されているArrayListクラスを使って書きました。ここでは、そんな代用品を使わないで済むように、ちゃんとした「貯金箱」のクラスを作りましょう。

クラスを作る初めの一歩は、名前を決めることです。

Javaでは、クラスやメソッド、変数などに日本語の名前を付けても良いのですが、ここではすべて、英語の名前を付けることにしましょう。もし将来、あなたの作ったプログラムが、世界中の人に使われることになっても大丈夫なように。。。

ここでは「貯金箱」を作るのですから、名前は「SavingsBox」とします。

オブジェクト指向プログラミングでは、クラスやオブジェクトは、抽象的な意味での「もの」を表しています。ですから、クラスの名前は、たいていは名詞になります。

では、次のようなソースファイルを書いて、「SavingsBox.java」という名前のファイルに保存してください。

class SavingsBox {
}

Javaでは、ファイル名は何でも良いのですが、クラス名と一致させておくのが良いでしょう。このソースファイルをコンパイルすると、「SavingsBox.class」というクラスファイルが生成されます。クラスファイルの名前は、クラス名と一致したものになりますから、ソースファイルも、同じ名前にしておいた方が無難です。

※ここでは、クラスのアクセス修飾子を付けていないので、クラス名と異なるファイル名を付けられました。ですが、publicなクラスの場合は、クラス名とファイル名は一致しなければなりません。

このクラスはまだ空っぽで、何もできませんが、正真正銘、世界で唯一のオリジナルのクラスです。

メソッドを決める

クラスの名前が決まったら、次は、そのオブジェクトをどうやって使うか、何ができるのか、を考えます。

Javaでは、オブジェクトに対してメソッドを呼び出すことで、オブジェクトを操作できます。このSavingsBoxクラスは空っぽですから、このままでは何もできません。貯金箱として使えるように、メソッドを作る必要があります。

ここでは例として、

  • 100円玉を入れる。
  • 500円玉を入れる。
  • 合計金額を調べる。

という、3つの操作ができるように、3つのメソッドを作ることにしましょう。

1つのメソッドを作るには、下記の3点を決める必要があります。

  • メソッドの名前。
  • 引数に何を渡すか。
  • 戻り値に何を返すか。

では、まずは「100円玉を入れる」メソッドについて、この3点を決めましょう。

メソッドの名前は、「100円玉を入れる」を英訳して、「insertCoin100」としましょう。

#メソッドは、オブジェクトに対して何かを行うものなので、基本的に、動詞から始まる名前を付けます。Javaでは慣例として、先頭の単語はすべて小文字で、2番目以降の単語は先頭のみを大文字にした名前を付けることが多いです。

引数は、無くても良いのですが、それだと、一度に1枚ずつしか入れられません。そこで、一度に何枚も入れられるように、100円玉の枚数を渡すことにしましょう。戻り値は、何も無しとします。

このメソッドを追加したソースファイルは、下記の通りになります。

class SavingsBox {
    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
    }
}

括弧の中に、引数を書きます。「int」型、つまり、整数の値を引数に渡す、という意味になります。引数で渡された値は、coinsという変数に格納されます。この変数の名前は、自由に付けられます。

戻り値は、メソッド名「insertCoin100」の前に書きます。ここでは「void」と書いてありますね。「void」とは、何も無い、ということを表す特別なキーワードです。ここでは、メソッドの戻り値が無い、という意味になります。

先頭に「public」というキーワードがあります。これは、アクセス修飾子と呼ばれるもので、このメソッドを誰が使えるか、を表しています。詳しくは、少し後でまた説明します。

同じように、「500円玉を入れる」メソッドと、「合計金額を調べる」メソッドも作りましょう。

class SavingsBox {
    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
    }

    // 500円玉を入れる。
    public void insertCoin500 ( int coins ) {
    }

    // 合計金額を調べる。
    public int getAmount ( ) {
    }
}

「合計金額を調べる」メソッドは、引数が無い代わりに、戻り値が「int」になっています。つまり、このメソッドを呼び出すと、合計金額が整数の値で戻ってくることになります。

さて、この3つのメソッドがあれば、100円玉と500円玉を入れる貯金箱として使えそうです。例えば、こんな風にプログラムを書いてみたらどうでしょう。

SavingsBox box = new SavingsBox();
box.insertCoin100(3);
box.insertCoin500(2);
int total_yen = box.getAmount();
System.out.println(total_yen);

100円玉を3枚、500円玉を2枚入れましたので、最後には「1300」と出力されるはずですね。

ですが、ここではまだ、メソッドの中身が空っぽですから、もちろん、このままでは動きません。この通りに動くように、メソッドの中身を作る必要があります。

#この時点では、コンパイルが通らなくなっています。

フィールドを用意する

ここまでは、このSavingsBoxクラスを使う利用者の立場に立って、クラス名やメソッドを決めてきました。ここからは、クラスの内部に目を向けます。

さきほど、100円玉を3枚、500円玉を2枚入れると、最後には「1300」と出力されるはずだ、と言いました。つまり、SavingsBoxクラスのオブジェクトは、貯金箱に100円玉や500円玉を入れると、それを覚えていてくれます。

オブジェクトが覚えているものを、フィールドとして定義します。フィールドとは、オブジェクトの中に入っている値や別のオブジェクトのことです。

SavingsBoxクラスでは、貯金箱に入れたお金の合計だけを覚えておけば良さそうです。そこで、フィールドとしては、合計金額を表す整数の値を1つだけ、用意することにします。

フィールドは、変数と同じように書きます。名前は「total」としましょう。

C++言語では、フィールドのことを「メンバー変数」と呼びます。なお、メソッドのことは「メンバー関数」と呼びます。

class SavingsBox {
    // 合計金額
    private int total;

    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
    }

    // 500円玉を入れる。
    public void insertCoin500 ( int coins ) {
    }

    // 合計金額を調べる。
    public int getAmount ( ) {
    }
}

int型のフィールド「total」が定義されました。その前に、「private」というキーワードが付いています。これもアクセス修飾子です。これについても、後ほど説明します。

メソッドの中身を作る

フィールドを用意したら、次はメソッドの中身を作ります。

100円玉を入れたり、500円玉を入れた時は、フィールド「total」に金額を足していけば良いですね。また、「合計金額を調べる」メソッドは、フィールド「total」の値を、そのまま返せば良いです。

この通りにメソッドを書くと、次のようになります。

class SavingsBox {
    // 合計金額
    private int total;

    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
        if (coins > 0) {
            total += coins * 100;
        }
    }

    // 500円玉を入れる。
    public void insertCoin500 ( int coins ) {
        if (coins > 0) {
            total += coins * 500;
        }
    }

    // 合計金額を調べる。
    public int getAmount ( ) {
        return total;
    }
}

「coins」には、メソッドが呼び出された時に、引数として渡された値が格納されています。「coins」は硬貨の枚数ですから、100円玉ならそれに100円を、500円玉なら500円を掛けて、合計金額に加えています。

なお、「coins」の値が0より大きいことをチェックしているのは、ちょっとした配慮です。マイナスの値を渡された時に、合計金額が減ってしまうのは、貯金箱の動きとしてはおかしいですからね。このように、クラスを作る時は、どのように利用されても良いように、いろいろなケースを想定して配慮する必要があります。

getAmountメソッドは、フィールドtotalの値をそのまま返しています。メソッドが戻り値を返す時は、「return」というキーワードを使います。

コンストラクタを作る

これで貯金箱のクラスはほぼ完成なのですが、1つ疑問があります。100円玉も500円玉も、どちらも1枚も入れずに、合計金額を調べるメソッドを呼び出すと、いくらになるのでしょうか?

実は、Javaでは、変数やフィールドは、自動的に初期化されます。int型であれば、必ず初期値は0になっています。ですから、硬貨を1枚も入れなかった時は、合計金額は0円になります。

この場合はこれで良いのですが、もし、初期値を0以外にしておきたいのであれば、コンストラクタと呼ばれる特別なメソッドを用意して、初期値をセットする必要があります。

コンストラクタは、キーワード「new」でオブジェクトが作成される時に、自動的に呼び出されるメソッドです。ふつうのメソッドと異なり、戻り値はありませんが、必要であれば、引数を持たせることができます。なお、コンストラクタの名前は、クラスの名前と同じにしなければなりません。

SavingsBoxクラスでもコンストラクタを用意すると、次のようになります。

class SavingsBox {
    // 合計金額
    private int total;

    // コンストラクタ
    public SavingsBox ( ) {
        total = 0;
    }

    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
        if (coins > 0) {
            total += coins * 100;
        }
    }

    // 500円玉を入れる。
    public void insertCoin500 ( int coins ) {
        if (coins > 0) {
            total += coins * 500;
        }
    }

    // 合計金額を調べる。
    public int getAmount ( ) {
        return total;
    }
}

これで、貯金箱のクラスは完成です。次のサンプルプログラムを動かしてみてください。

class MyFirstProgram {
    public static void main(String[] args) {
        SavingsBox box = new SavingsBox();
        box.insertCoin100(3);
        box.insertCoin500(2);
        int total_yen = box.getAmount();
        System.out.println(total_yen);
    }
}

ちゃんと「1300」と出力されます。

アクセス修飾子とカプセル化

最後に、アクセス修飾子についてお話ししておきましょう。

ここまで、「public」と「private」という、2種類のアクセス修飾子が出てきました。アクセス修飾子は、メソッドやフィールドを、誰が使えるか、を表したものです。「public」は、このクラスの外からでも使えるものを、「private」は、このクラスの中でしか使えないものを表しています。

#この説明は厳密には正しくない。後の章で詳しく解説する予定。

貯金箱のクラスでは、メソッドには「public」を、フィールドには「private」を付けていました。つまり、SavingsBoxを使う人は、メソッドを呼び出すことはできますが、フィールドを直接使うことはできません。

もしも、totalフィールドも、アクセス修飾子をpublicにしたら、どうでしょう。すると、次のようなことができるようになってしまいます。

SavingsBox box = new SavingsBox();
box.insertCoin100(3);
box.total = 12345;      // フィールドの値を直接書き換える。
int total_yen = box.getAmount();
System.out.println(total_yen);

ですが、これでは、100円玉を入れたり、500円玉を入れるメソッドを用意した意味がありません。いくらでもやりたい放題になってしまいます。マイナスの値だって入れられてしまいます。

貯金箱の中のお金を好き勝手に書き換えられないようにして、『100円玉と500円玉だけを入れられる貯金箱である』という意味を明確にするために、totalフィールドのアクセス修飾子をprivateにして、直接値を書き換えられないように制限をしているのです。

オブジェクトの操作方法を制限するのには、クラスの意味を明確にすることの他に、クラス内部の作りを隠蔽する、という意味もあります。

例えば、このSavingsBoxクラスは、次のように書いても、まったく同じように動きます。

class SavingsBox {
    // 100円玉の枚数
    private int total100;

    // 500円玉の枚数
    private int total500;

    // コンストラクタ
    public SavingsBox ( ) {
        total100 = 0;
        total500 = 0;
    }

    // 100円玉を入れる。
    public void insertCoin100 ( int coins ) {
        if (coins > 0) {
            total100 += coins;
        }
    }

    // 500円玉を入れる。
    public void insertCoin500 ( int coins ) {
        if (coins > 0) {
            total500 += coins;
        }
    }

    // 合計金額を調べる。
    public int getAmount ( ) {
        return total100 * 100 + total500 * 500;
    }
}

さきほどまでのSavingsBoxクラスのソースファイルと比べると、フィールドはまったく別物に変わっていますし、すべてのメソッドの中身も書き換えられてしまっています。ですが、このクラスを利用する人は、その違いを気にする必要はありません。何故なら、メソッドの呼び出し方は変わっていませんし、結果も同じだからです。

もしも、フィールドのアクセス修飾子をpublicにしていたら、どうでしょう。誰かが、totalフィールドを直接書き換えるようなプログラムを書いていたかもしれません。すると、SavingsBoxクラスを書き換えてしまうと、それを使っている他の人のプログラムまで、すべて書き直さないといけない羽目になってしまいます。

このように、アクセス修飾子を使って、クラスを利用する人に見せる部分を制限し、見せる必要がない部分を隠蔽することを、カプセル化と呼びます。

#「カプセル化」は、データと手続きを一体化する、という意味もあるので、ここの記述は適切ではないかもしれない。

すべてをpublicにした方が自由に使えて便利に思えるかもしれませんが、そうではないのです。カプセル化は、オブジェクト指向プログラミングではとても重要な考え方ですので、ぜひ身に付けてください。

※アクセス修飾子について、Javaではクラスレベルでの制限になりますが、他のオブジェクト指向言語Smalltalk, Eiffel, Ruby)では、インスタンスレベルでの制限になるそうです。例えば、Javaでは、同じクラスのインスタンスであれば、他のインスタンスのprivateメンバーを参照できますが、Smalltalk, Eiffel, Rubyでは、同一クラスのインスタンスでも、他のインスタンスのprivateメンバーにはアクセスできないそうです。