この記事では、当社 の新人エンジニア研修の参考にJava8を解説します。
前回は継承(拡張)について解説しました。
今回はカプセル化について解説します。
カプセル化は情報隠蔽とセットなので情報隠蔽についてもお話しします。
これらもオブジェクト指向特有の概念です。
カプセル化とは、情報と処理を1つにまとめることです。
Javaで言えば、フィールドとメソッドが一つのクラスにまとまっていますからカプセル化はされてます。
また、カプセル化は情報隠蔽とセットになっています。
情報隠蔽については、たとえ話をさせてください。
オブジェクト指向はプログラムを部品と考えて、その部品の組み合わせで大きなプログラムを作るのでした。
例えば、パソコンにモニターやマイク、プリンターをつないで一つのシステムにするイメージでしたね。
その時、私たちは個々の部品、パソコンやモニターの中身に直接触れることができるでしょうか?
もちろん、強引にやれば可能ですが(良い子の皆さんはぜひ試してみてください)
保証適用外になりますね。
逆に言えば、中身に直接触れさせないことによって部品と更には皆さんを守っているということもできます。
このイメージが情報隠蔽です。
この記事では、カプセル化に情報隠蔽の意味も含めて解説します。
1.カプセル化(情報隠蔽)の例
例によって、JavaのAPIにその範を求めましょう。
前回、hashCodeメソッドまでさかのぼりましたね。
実は、このhashCodeメソッドはStringクラスも持っています。
StringクラスのhashCodeメソッドを使ってこんなことができます。
1 2 3 4 5 6 |
public class Example37 { public static void main(String[] args) { System.out.println("Hello".hashCode()); } } |
<結果>
69609650 |
では、このStringクラスのhashCodeメソッドの定義をさかのぼりましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
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; } |
ここで注目いただきたいのは2行目、3行目のhash、valueという値です。
※9行目でハッシュ値が更新されているところも念のため確認ください
また、定義にさかのぼりましょう。
1 2 3 4 5 |
/** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 |
例えば、一番下のprivate int hashを見てください。
privateとして宣言していますね。
つまり、非公開ということでこのクラスからのみアクセスできる変数(フィールド)であるという宣言です。
ですから、以下のようなことはできません。
1 2 3 4 5 6 7 |
public class Example37_1 { public static void main(String[] args) { int i = "Hello".hash = 1; System.out.println(i); } } |
4行目でエラーが出ているはずです。
強引に実行すれば、
Uncompilable source code – hashはjava.lang.Stringでprivateアクセスされます |
と出力されます。
つまり、このように「フィールドの値を更新するときには必ずメソッドを使って更新し、フィールドには直接アクセスできなくすること」を情報隠蔽といいます。
公開すべきものと非公開にすべきものを明確に分け、公開したものだけを使ってプログラミングをするようにプログラマに促しているのです。
2.アクセス修飾子
private 修飾詞は、他のクラスからアクセスできない(不可視)という意味です。
このように、他のクラスからインスタンス変数を隠すことを「情報隠蔽」といいました。
修飾子とは、クラス、フィールド、メソッドの性質を指定するものをいうのでした。
その中でも特に、アクセスを制御するためのものをアクセス修飾子と呼びます。
フィールドとメソッドのアクセス修飾子を公開範囲の広いものから狭いものに並べて一覧にすると以下のようになります。
アクセス修飾子 | 公開範囲 |
public | どのクラスからもアクセスできる。 |
protected | 現在のクラスとサブクラスからアクセスできる |
なし | 現在のクラスと同じパッケージ(後述)のクラスからアクセスできる |
private | 現在のクラスからだけアクセスできる |
ここでは、オリジナルなクラスに情報隠蔽を施してみます。
例えば、カジノゲームを作成するとします。
プレイヤー(Player)は最初にチップの残高(balance)を1,000持ってゲームスタートです。
プレイヤーはこの残高からチップを引き出すことができますが、残高がマイナスになることはできません。
それを表現したのが以下のサンプルプログラムです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Player { private int balance = 1000; void withdraw(int amount) { if ((balance - amount) < 0) { System.out.println("残高不足です"); } else { balance -= amount; System.out.println("現在の残高です:" + balance); } } } public class Example37_2 { public static void main(String[] args) { Player p1 = new Player(); p1.withdraw(200); p1.withdraw(1000); } } |
<結果>
現在の残高です:800 残高不足です |
このようにカプセル化を使ってフィールドの値を守ることができるのです。
他のプログラマ(含む将来のあなた自身)に設計者が意図しない操作をさせない、それがカプセル化の効果です。
また、フィールドにアクセスするためのメソッドをアクセサメソッド(accessor method)といいます。
Javaの基本的なクラス設計は、フィールドはprivate、メソッドはアクセサメソッドとして、publicにするというものです。
サンプルプログラムとして、足し算電卓のクラスを作成してみます。
あくまで説明のためのサンプルですので隔靴掻痒の感は免れないところですが。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
class Calculator { private int num1, 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; } public int add() { return this.num1 + this.num2; } } public class Example37_3 { public static void main(String[] args) { Calculator cal = new Calculator(); cal.setNum1(8); cal.setNum2(128); System.out.println(cal.add()); } } |
<結果>
136 |
このアクセサメソッドについては、後でJSP/サーブレットを学ぶ際に大活躍しますので、お楽しみに。
※ここでは、メソッドは無条件にpublicにすべきとしているように感じられるかもしれません。しかし、本当は違います。メソッドをprivateにした方が良い場合というもの存在します。例えば、同じクラスからしか使わないメソッドはprivateにします。メソッドをpublicにするデメリットは、そのメソッドを使うクラスがあった場合メソッドのインプット・アウトプットを変更しづらくなってしまうということが挙げられます。(依存性の問題)
4.クラスとインターフェースのアクセス修飾子について
クラスやインターフェースもアクセス修飾子を使ってパッケージ外からのアクセスを制御することができます。
公開するか公開しないかの2択ですので分かりやすいですね。
アクセス修飾子 | 意味 |
public | 全てのパッケージからアクセス可能 |
なし | 同じパッケージからのみアクセス可能 |
ここでの注意点としては、クラスのアクセス修飾子はフィールドとメソッドのアクセス修飾子より優先されるということです。
つまり、例えばフィールドやメソッドをpublic宣言してもクラスがpublic宣言されていないと、そのフィールドやメソッドは他のパッケージからはアクセスできません。
そもそも、クラスにアクセスできないわけですから当然と言えば当然ですが。
また、public修飾子をつけられるクラスは1つのjavaファイルの中に1つだけです。
さらに、public修飾子をつけたクラス名とファイル名は一致する必要があります。
これまでのサンプルプログラムもそうなっていたことに気づかれた方はいますか?
5.final修飾子とは
では、次にカプセル化とは話がずれるのですが、final修飾子についてお話ししたいと思います。
final修飾子は、後から変更してはいけないものに付けます。
これまでの学習では、
クラスMathのPIがありました。
public static final double PI |
初期化したら変更できない定数になるのでしたね。
また、このページの上で文字列を格納するvalueというchar配列がfinalです。
1 2 |
/** The value is used for character storage. */ private final char value[]; |
文字列はイミュータブルで変更できないというのはこのように宣言されているからです。
また、クラスにfinalをつけるとサブクラスを作れなくなります。
finalなクラスの代表例として、Stringが挙げられます。
上記のリンクからAPIに移動してみてください。
public final class String
となっていますね。
つまり、Stringを拡張してオリジナルなサブクラスは作れないのです。
誰もが勝手に自分仕様の文字列クラスを作成すると混乱するからでしょうね。
それから、Mathもfinalクラスです。
finalなクラスというのは案外あります。
さらに、メソッドにfinalをつけるとサブクラスでオーバーライドできないメソッドになります。
クラスは継承させたいが、メソッドはオーバーライドさせたくないケースということになります。
具体的にはオーバーライドされたらまずいメソッドにfinal宣言をするケースはあり得ると思います。
しかし、新入社員のうちは特に気にしなくてよいでしょう。
これをまとめると以下のようになります。
<finalキーワードの意味>
finalクラス | 継承できない |
final変数(フィールド、ローカル問わず) | 定数(再代入できない) |
finalメソッド | オーバーライドできない |
今回はカプセル化について見てきました。
カプセル化することでオブジェクトは堅牢になるのでした。
前回見た、継承とポリモーフィズム、そして今回のカプセル化の3つをあわせてオブジェクト指向の三大要素ということがあります。
次回のテーマは抽象クラスです。
抽象クラスはインスタンス化できないクラスです。
インスタンス化できないクラスにどんな存在意義があるというのでしょうか?
よくよく考えると今まで学んだポリモーフィズムと継承(オーバーライド)の中にそのヒントがあります。
推理しながら次回を楽しみに待っていてください。
JavaSE8の解説に戻る