Print Friendly, PDF & Email

新人エンジニア研修で知っておきたいカプセル化の使い方

なぜ、カプセル化の理解が重要なのか、その理由。

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回は継承(拡張)について解説しました。

今回はカプセル化について解説します。

カプセル化はデータ隠蔽とセットなのでデータ隠蔽についてもお話しします。

これらもオブジェクト指向特有の概念です。

カプセル化とは、関連する情報と処理を1つにまとめることです。

オブジェクト指向はプログラムを部品と考えて、その部品の組み合わせで大きなプログラムを作るのでした。

例えば、車でもパソコンでも関連する部品は近くに配置されていますね。

ですのでエンジン廻りだけを取り替える、デスクトップパソコンの電源ボックスだけを取り替えるということが可能です。

物理的なモノの場合は、距離の問題がありますから、関連の近いものを、近いところに並べるというのは自然です。

しかし、情報の世界はそうではありませんから、気をつけないとグチャグチャになりがちなのです。

データ隠蔽とは、クラスのメンバ(フィールドやメソッド)の公開範囲をできるだけ限定することです

私たちは個々の部品、車やパソコンの中身(やそのデータ)に直接触れることができるでしょうか?

もちろん、強引にやれば可能ですが(良い子の皆さんはぜひ試してみてください

保証適用外になりますね。

逆に言えば、中身に直接触れさせないことによって部品と更には皆さんを守っていると言うこともできます。

このイメージがデータ隠蔽です。

この記事では、カプセル化にデータ隠蔽の意味も含めて解説します。

1.カプセル化の意義

カプセル化とは「関連する」情報と処理を一つのクラスにまとめることでした。

「今まで作ったクラスはどれもメンバとしてフィールドとメソッドを持っているのでカプセル化ができている」新人エンジニアであるあなたは、そう思ったかもしれません。

ポイントは“関連する”というところです。

例えば次のような一台のノートパソコンを表すクラスはどうでしょうか?

class LaptopPC{
int id;
String name;
String companyName;
String companyAdress;
String companyTelephoneNumber;
・・・
}

IDと名前までは良いとしても会社名や会社住所、会社電話番号などはLaptopPCクラスが持つべきデータではないですね。

Companyクラスを別途作るべきでしょう。

クラスの設計の考え方には単一責任の原則というものがあります。

1つのクラスの仕事は1つにせよということです。

上記の例では会社の住所や電話番号を取得するにもLaptopPCクラスに問い合わせるということになってしまいます。

また、社名が変更になった場合や会社が引っ越した場合にはLaptopPCクラスを修正することになってしまいますね。

上記の例とは逆に、クラスに一切フィールドを持たせずに、メソッドだけでクラスを作成することもカプセル化の考え方に反しています。

手続き指向の関数やサブルーティンになってしまいます。

2.データ隠蔽の意義

データ隠蔽の意義は、不用意にデータに触らせないことにありました。

例によって、Javaの標準APIにその範を求めましょう。

前回、StringクラスのhashCodeメソッドまで遡りましたね。

なお、新人エンジニア研修でこの hashCodeメソッド の仕組みを理解することは求められませんので参考までにお聞き下さい。

StringクラスのhashCodeメソッドを使ってこんなことができます。

package chap11;

public class Example01 {

    public static void main(String[] args) {
        System.out.println("A".hashCode());
        System.out.println("AA".hashCode());
    }
}

<結果を表示する>

65
2080

“65”は文字Aの文字コードを10進表記したものです。

しかし、“2080”はどこから来たのでしょうか?

では、IDEを使ってこのStringクラスのhashCodeメソッドの定義をさかのぼりましょう。

以下のコードにあるhashは

  /** Cache the hash code for the string */
    private int hash; // Default to 0

とありますので、デフォルトは0です。

また、valueは以下の通りStringのインスタンスを格納するchar型の配列です。

"private"で宣言されていることが情報隠蔽と関連していますので心に留めてください。

    /** The value is used for character storage. */
    private final char value[];

その上で以下のhashCodeメソッドを読んでみて下さい。

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

このソースコードから分かるのは、先の65という数値は

31 *  0 + 65

の結果であり、

2080という数値は

31 *  65 + 65

の結果であるということです。(なぜ、31を掛けるのか、興味のある方はググってみてください)

ここで本題のカプセル化に戻ります。

hashは以下のように"private"で宣言されていました。

  /** Cache the hash code for the string */
    private int hash; // Default to 0

つまり、非公開ということで、「このクラスからのみアクセスできるフィールドである」という宣言です。

他クラスからのアクセスを拒否するという意味です。

ですから、以下のようなことはできません。

package chap11;

public class Example02 {

    public static void main(String[] args) {
        "A".hash = 1;
    }
}

上記のプログラムにはエラーが出ているはずです。

強引に実行すれば、

<結果を表示する>

Uncompilable source code - hashはjava.lang.Stringでprivateアクセスされます

と出力されます。

ハッシュ値の性質からそれを強引に書き換えられたとしたらセキュリティ問題や整合性の問題が発生することは容易に想像できるのではないでしょうか?

つまり、このようにフィールドの値を更新するときには必ずメソッドを使って更新し、フィールドには直接アクセスできなくすること」がデータ隠蔽です

公開すべきものと非公開にすべきものを明確に分け、公開したものだけを使ってプログラミングをするようにプログラマに促しているのです。

3.アクセス修飾子

private 修飾詞は、他のクラスからアクセスできない(不可視)という意味です。

このように、他のクラスからインスタンス変数を隠すことを「データ隠蔽」といいました。

また、修飾子とは、クラス、フィールド、メソッドの性質を指定するものをいうのでした。

その中でも特に、アクセスを制御するためのものをアクセス修飾子と呼びます。

フィールドとメソッドのアクセス修飾子を公開範囲の広いものから狭いものに並べて一覧にすると以下のようになります。

アクセス修飾子公開範囲クラス図での表記
publicどのクラスからもアクセスできる+
protected現在のクラスとサブクラスからアクセスできる#
なし現在のクラスと同じパッケージのクラスからアクセスできる~
private現在のクラスからだけアクセスできる-

以下の図は、クラスAの3つのフィールドに対して、どのクラスからアクセス可能かを矢印(➞)を使って示しています。

同じクラスのメソッドからは全てのフィールドにアクセス可能です。

同じパッケージにあるクラスBからはアクセス修飾子が“なし”のフィールドにアクセス可能です。

クラスCはクラスAのサブクラスなので、アクセス修飾子が“protected”のフィールドにアクセス可能です。

なお、 protected は弊社の新人エンジニア研修では(ほとんど)必要ありません。

この図にはありませんが、“public”のフィールドには全てのクラスからアクセスが可能です。

アクセス修飾子を新人エンジニア研修向けに解説
図解:public以外のアクセス修飾子

公開範囲外からのアクセスはコンパイルエラーとなります。

ここでは、オリジナルなクラスにデータ隠蔽を施してみます。

例えば、カジノゲームを作成するとします。

プレイヤー(Player)は最初にチップの残高(balance)を1,000持ってゲームスタートです。

プレイヤーはこの残高からチップを引き出すことができますが、残高がマイナスになることはできません。

それを表現したのが以下のサンプルプログラムです。

package chap11;

public class Player {

   private int balance = 1000;

   public void withdraw(int amount) {
        if ((balance - amount) < 0) {
            System.out.println("残高不足です");
        } else {
            balance -= amount;
            System.out.println("現在の残高です:" + balance);
        }
    }
}
package chap11;

public class Example03 {

    public static void main(String[] args) {
        Player p1 = new Player();
        
        p1.withdraw(200);
        p1.withdraw(1000);
    }
}

<結果を表示する>

現在の残高です:800
残高不足です

このようにカプセル化を使ってフィールドの値を守ることができるのです。

他のプログラマ(含む将来のあなた自身)に設計者が意図しない操作をさせない、それがカプセル化の効果です。

また、フィールドにアクセスするためのメソッドをアクセサメソッド(accessor method)といいます。

Javaの基本的なクラス設計には、フィールドはprivate、メソッドはアクセサメソッドとして、publicにするというものがあります。

アクセサメソッドを使ったサンプルプログラムとして、足し算電卓のクラスを作成してみます。

あくまで説明のためのサンプルですので冗長でまどろこしい処理ですがご容赦ください。

package chap11;

public class Calculator {

    private int num1, num2;

    public int add() {
        return this.num1 + this.num2;
    }

    public int getNum1() {
        return num1;
    }

    public void setNum1(int num1) {
        this.num1 = num1;
    }

    public int getNum2() {
        return num2;
    }

    public void setNum2(int num2) {
        this.num2 = num2;
    }
}
package chap11;

public class Example04 {

    public static void main(String[] args) {
        Calculator cal = new Calculator();
        cal.setNum1(8);
        cal.setNum2(128);
        System.out.println(cal.add());
    }
}

※メソッドgetNum1,2は使っていません。

<結果を表示する>

136

アクセサメソッドもIDEで簡単に挿入できますので試してみてください。

このアクセサメソッドについては、後でWebアプリケーションを学ぶ際に活躍しますので、お楽しみに。

※ここでは、メソッドは無条件にpublicにすべきとしているように感じられるかもしれません。しかし、本当は違います。メソッドをprivateにした方が良い場合というもの存在します。例えば、同じクラスからしか使わないメソッドはprivateにします。メソッドをpublicにするデメリットは、そのメソッドを使う他クラスがあった場合にメソッドのインプット・アウトプットを変更しづらくなってしまうということが挙げられます(依存性の問題)。余計な公開はしないというのがデータ隠蔽の考え方です。また、アクセサメソッドはカプセル化を壊す作用があります。むやみに作るべきではありません。

4.クラスとインターフェースのアクセス修飾子について

クラスやインターフェース(未出)もアクセス修飾子を使ってパッケージ外からのアクセスを制御することができます。

公開するか公開しないかの2択ですので分かりやすいですね。

アクセス修飾子意味
public全てのパッケージからアクセス可能
なし同じパッケージからのみアクセス可能

ここでの注意点としては、クラスのアクセス修飾子はフィールドとメソッドのアクセス修飾子より優先されるということです。

つまり、例えばフィールドやメソッドをpublic宣言してもクラスがpublic宣言されていないと、そのフィールドやメソッドは他のパッケージからはアクセスできません。

そもそも、クラスにアクセスできないわけですから当然と言えば当然ですが。

また、public修飾子をつけられるクラスは1つのjavaファイルの中に1つだけです。

さらに、public修飾子をつけたクラス名とファイル名は一致する必要があります。

これまでのサンプルプログラムもそうなっていたことに気づかれた方はいますか?

5.final修飾子とは

※この話題は研修時間の都合によりスキップすることがあります。

では、次にカプセル化とは話がずれるのですが、final修飾子についてお話ししたいと思います。

これまでもちょくちょく出てきたとは思うのですが、ここでまとめです。

finalは「最後の」という意味ですが、まさにそんな感じです。

これまでの学習では、

クラスMathのPI(標準API)がありました。

public static final double PI = 3.14159265358979323846;

初期化したら変更できない定数になるのでしたね。

また、先のStringクラスのところで出てきた文字列を格納するvalueというchar配列がfinalでしたね。(再掲)

    /** The value is used for character storage. */
    private final char value[];

文字列はイミュータブルで変更できないというのはこのように宣言されているからです。

また、クラスにfinalをつけるとサブクラスを作れなくなります。

finalなクラスの代表例として、String(標準API)が挙げられます。

上記のリンクから標準APIに移動してみてください。(あるいはIDEでソースコードを見てください)

public final class String

となっていますね。

つまり、Stringを拡張してオリジナルなサブクラスは作れないのです。

誰もが勝手に自分仕様の文字列クラスを作成すると混乱するからでしょうね。

それから、Mathもfinalクラスです。

さらに、メソッドにfinalをつけるとサブクラスでオーバーライドできないメソッドになります。

クラスは継承させたいが、メソッドはオーバーライドさせたくないケースということになります。

具体的にはオーバーライドされたらまずいメソッドにfinal宣言をするケースはあり得ると思います。

しかし、新入エンジニア研修の間は特に気にしなくてよいでしょう。

これをまとめると以下のようになります。

<finalキーワードの意味>

finalクラス継承できない
final変数(フィールド、ローカル問わず)定数(再代入できない)
finalメソッドオーバーライドできない

できない、できない、できない、とまさにfinalですね。

今回はカプセル化について見てきました。

カプセル化することでオブジェクトは堅牢になるのでした。

前回見た、継承とポリモーフィズム、そして今回のカプセル化の3つをあわせてオブジェクト指向の三大要素ということがあります。

次回のテーマは抽象クラスです。

抽象クラスはインスタンス化できないクラスです。

インスタンス化できないクラスにどんな存在意義があるというのでしょうか?

よくよく考えると今まで学んだポリモーフィズムと継承(オーバーライド)の中にそのヒントがあります。

推理しながら次回を楽しみに待っていてください。

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□カプセル化とは、関連する情報と処理を1つにまとめること

□データ隠蔽とは、クラスのメンバ(フィールドやメソッド)の公開範囲をできるだけ限定すること

□「フィールドの値を更新するときには必ずメソッドを使って更新し、フィールドには直接アクセスできなくすること」がデータ隠蔽

□他のプログラマに設計者が意図しない操作をさせない、それがカプセル化の効果である

□Javaの基本的なクラス設計は、フィールドはprivate、メソッドはアクセサメソッドとして、publicにするものが多い

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題11.カプセル化

ここでは、カプセル化に関するJavaプログラムを作成してみましょう。 1.カプセル化 以下のクラスにカプセル化(含むデータ隠蔽)を施しなさい。 IDEの便利機能を使用して…

【今回の復習Youtube】

020-オブジェクト指向の基本-カプセル化

JavaSE8の解説に戻る

最後までお読みいただきありがとうございました。

新人エンジニア研修のプログラムを見てみる

当社は研修講師を募集中です(サブ講師のため未経験者可)