ほぷしぃ

Java言語入門 〜C言語を学んだ君へ〜

[第8回]クラス

[1] カプセル化

第7回でカプセル化の説明を簡単にしました。
そして、前のページでクラスの基本的な作成方法を説明しました。
では、いよいよJavaプログラムを用いたカプセル化を学びましょう。

カプセル化を行うためには?

カプセル化を行うために必要なものは、クラスとデータと手続きの3つです。
Javaでは、データをメンバ変数、手続きをメソッドと前回説明しました。
メンバ変数とメソッドをクラスが包み込み、カプセル化を実現させます。
ここで、前のページで作成した、メンバ変数、メソッドを使ったHumanクラスを見てみましょう。

サンプルプログラム

class Human {
    // メンバ変数
    String name;
    int age;

     // コンストラクタ
     Human(String n, int a) {
        name = n;
        age = a;
    }

     // メソッド
     void introduce() {
        System.out.println("私の名前は" + name + "で年齢は" + age + "です。");
     }
}

プログラム解説

このクラスにはカプセル化が適応されておらず、実は全くのダメクラスです。
では、このクラスの問題点は何か、どうすればカプセル化が適応されるかを次の項目以降で学びましょう。

[2] Humanクラスの問題点を改善

今のクラスのままではダメクラスのままなので、カプセル化を行わなくてはなりません。
その前に、カプセル化とはなんなのか学びましょう。

カプセル化の利点

プログラムミスの減少(プログラムの影響範囲の削減)
データの隠蔽(誤ったデータアクセスの禁止)

カプセル化により、データなどを隠蔽することで、誤ったデータの書き換えを未然に防ぐことができます。
また、アクセスできる範囲を限定することによりプログラムミスの減少が期待できます。
以上のことを念頭に置き、Humanクラスの問題を考えていきましょう。

Humanクラスの問題点

このクラスでは、メンバ変数に年齢をつけました。

int age;

年齢はコンストラクタで設定し、前回は「10歳の太郎」を生成しました。
一見、問題はなさそうですが、次のようにオブジェクトを生成(インスタンス化)することもできてしまいます。

Human human = new Human("太郎", -1);// 年齢を−1歳に設定

「−1歳の太郎」。・・・・・・・・そんな人はありえません。

人として、年齢がマイナスになるなんてことはありません。
なんだ、そんなことかと、ばからしい間違いに見えるかもしれませんが、
実際に何千行・何万行という長いソースを書くと、このような間違いが起こる可能性があります。
また、商品の在庫数がマイナス表記なんてありえません。
このような場合はデータの入力場所に修正を加えます。
今回の場合はコンストラクタで誤ったデータを未然に防ぎます。
これで、1つ目の問題が解決できます。次のように変更をしてください。

コンストラクタの修正

    // コンストラクタ
    Human(String n, int a) {
         name = n;
        if (a < 0) {
            age = 0;// マイナスの年齢はすべて0に初期化する
        } else {
            age = a;
        }
    }

プログラム解説

上のようにマイナスの年齢という誤った年齢が入力できないように制御します。
0よりも小さい値が来た時にifを使って0にしています。
それ以外の値のときはそのままageに代入を行います。
これで「-1歳の太郎」問題が解決しました。
今回はコンストラクタでしたが、もし、年齢を入力するメソッドがあったら、そのメソッド内で上のような処理を行います。

まだ終わらない問題点

1つ解決できましたが、問題はまだあります。
それは、Humanクラスの年齢へのデータ入力の方法が、コンストラクタ以外にもあるということです。
つまり、次のようにメンバ変数を参照できます。
今まで説明していませんでしたが、メンバ変数への参照は以下のように行います。

インスタンス名.メンバ変数名

前のページで太郎とホップの2人を生成しましたが、
実はこのように各オブジェクトによって使い分けられているのです。
前のプログラムではこのように使うことになります。

human1.age = 20;  //インスタンスhuman1.ageにアクセス
System.out.println(human1.age);

このようにすることでint ageにアクセスすることができます。
また、System.out.println()で値を表示することもできます。
メソッド名の部分がメンバ変数になっただけですので覚えやすいでしょう。
では、いったいこのままだと何がいけないのでしょうか。

Human human = new Human("太郎", 10) // 10歳の太郎を作成

ここまでは特に問題ありません。しかし・・・

human.age = -1; // 年齢を10歳から−1歳に変更

何の制御もなく、簡単に年齢が変更できてしまいます。
これでは、また、「-1歳の太郎」が出来上がってしまいます。
折角、コンストラクタで制御したのに、これではなんの意味もありません。
しかし、対処方法はあります。それがアクセス修飾子です。
次の項目では、このアクセス修飾子について説明します。

[3] アクセス修飾子

アクセス修飾子とは、アクセスを制御するものです。
これを使うことで、カプセル化を適応したHumanクラスが作成できます。
アクセス修飾子の種類は3つあります。
これをメンバ変数、コンストラクタ、メソッドに付け加えます。

アクセス修飾子の使い方

アクセス修飾子 データ型 変数名;

アクセス修飾子 コンストラクタ名 (引数・・・) {....}

アクセス修飾子 戻り値 メソッド名(引数・・・) {....}

先頭にアクセス修飾子をつけましょう。
アクセス修飾子をつけた場合とつけない場合の機能を覚えてください。
では、アクセス修飾子にはどのような種類があるのか次の表で確認してください。

アクセス制御方法の一覧

アクセス修飾子制御の強さ機能
public弱い全てのクラスからアクセスを許可
protected少し弱い同じパッケージ、継承先からのアクセスを許可
private強い同じのクラスからのアクセスを許可
なし少し強い同じパッケージからのアクセスを許可

アクセス修飾子の機能にパッケージ、継承と書いてありますが、
今は気にしないでください。これらは後の回で説明します。
ここで、覚えてもらいたいことは、アクセス制御の強さが

private > アクセス修飾子なし > protected > public
強い←・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・・→弱い

の順であることです。
そして、アクセス修飾子「private」を用いれば、
他クラスからのアクセスを全て禁止することができるということです。
他からアクセス(参照)されたくないものに「private」をつけましょう。

これで、先ほど太郎が-1歳になってしまった処理を防ぐことができるようになります。
ところで、「protected」と「指定なし」は今は使いません。
まずは、「private」と「public」の使い分けを覚えてください。
「protected」、「指定なし」は第11回「パッケージ」で学習します。

ここで、話をHumanクラスに戻し、メンバ変数ageにprivateを適用させます。
以下のように今のプログラムに変更を加えてください。

プログラムの修正2

class Human {
    // メンバ変数
    private int age; // アクセス修飾子privateを適用
    :
    :
}

解説

これでメンバ変数ageは他のクラスから参照されなくなります。
もし、他のクラスから次のような記述をしても、コンパイルエラーが起きます。

Human human = new Human("太郎", 10) // 10歳の太郎を作成
human.age = -1;                                 // ここでコンパイルエラーが起きる

これで、「-1歳太郎」問題は無事に解決できました。

[4] カプセル化を適当したHumanクラス

さっそくカプセル化を適応したHumanクラスを作成しましょう。
すべてのメンバ変数・コンストラクタ・メソッドにアクセス修飾子を付けます。

Humanクラスからのアクセス制御の変更点

1.メンバ変数nameとageにアクセス修飾子「private」をつける

private String name;
private int age;

メンバ変数は勝手に変更できないように「private」をつけます。
これで勝手な代入を防ぐことができます。
-1歳太郎を生成することができなくなるわけです。

2.コンストラクタHumanにアクセス修飾子「public」をつける

public Human(String n, int a){…}

3.メソッドvoid introduce()にアクセス修飾子「public」をつける

public void introduce(){….}

コンストラクタ、メソッドの2つは、どのクラスから参照されても問題ないからです。

これで終わりと言いたいところですが、アクセス修飾子をつけたことで1つ問題が起きました。
それは、後からメンバ変数の変更ができないということです。

× human1.age = 11;

このプログラムを例にとると、10歳の太郎を生成したら、ずっと10歳のままです。
これも-1歳がありえないように、年齢(メンバ変数)が増えない人間はいません。
名前は変更できないとしても、太郎に誕生日がきたら、年齢(メンバ変数)が変わるのは当然です。
そのとき、年齢(メンバ変数)を変更させなくてはなりません。

"private int age"を変更する方法はメソッドにあります。

メソッド、すなわち手続きとは、カプセル化の出入り口にあたると説明しました。
次のメソッドをHumanクラスに追加します。

プログラムの修正3

    // 入口
    public void setAge(int a) {
        if (a < 0) {
            age = 0;
        } else {
            age = a;
        }
    }

    // 出口
    public int getAge() {
        return age;
    }

プログラムの修正3の解説

これでメンバ変数ageにアクセス制御をかけつつ、値の変更、値の取り出しができるようになります。
もちろん、値を変更する場合は誤った値を入力できないように制御します。
「private」にしたメンバ変数は同じクラス内からアクセスできないため、そのクラス内で変更、参照できるようにします。
メソッドは基本的にこのような使い方をします。
どうですか、C言語の関数とは別物でしょう。

ところで、コンストラクタもSetAgeメソッドも同じことをやっています。
そこで、コンストラクタを次のように書き直します。

プログラムの修正4

    public Human(String n, int a) {
        name = n;
        setAge(a); // 年齢の設定はメソッドに任せる
    }

プログラムの修正4の解説

変更点は以上です。
最後に変更し終えたHumanクラスのソースを示します。
カプセル化が適応されようやくまともなクラスを作成することができました。

カプセル化を行ったプログラム

class Human {
    // メンバ変数
    private String name;
    private int age;

    // コンストラクタ
    public Human(String n, int a) {
        name = n;
        setAge(a); // 年齢の設定はメソッドに任せる
    }

    // メソッド
    public void introduce() {
        System.out.println("私の名前は" + name + "で年齢は" + age + "です。");
    }

    // 入口
    public void setAge(int a) {
        if (a < 0) {
            age = 0;
        } else {
            age = a;
        }
    }

    // 出口
    public int getAge() {
        return age;
     }
}

public class Java08_02{
    public static void main(String args[]){
        Human human1 = new Human("太郎",-5);
        human1.introduce();

        //human2.age = 10; privateがついているので直接代入できない
        //変わりに年齢を代入する専用のメソッドを使う
        human1.setAge(10);

        human1.introduce();
    }
}

実行結果

実行結果
以上で、問題を解決することができました。
カプセル化はオブジェクト指向プログラミングの重要な概念ですので、忘れないでください。



前のページへ ページのトップへ 次のページへ