Javaでプログラミングを記述する際に「カプセル化」を行うことが基本となります。
この記事ではカプセル化の概要や特徴を説明します。このサイト内でのカプセル化は「データ隠ぺい」に重きを置いた説明(狭義のカプセル化)となっているので、広義のカプセル化はwikiのカプセル化をご覧ください。
もくじ
カプセル化とは
まずカプセル化は特殊な構文ではなく、プログラムを記述する際の考え方およびルールみたいなものです。カプセル化は簡潔にまとめると以下のようになります。
- フィールドにアクセス修飾子「private」を付与する
- フィールドにアクセスを行うためのメソッドを作成する(アクセサメソッドなどと呼ばれる)
フィールドのprivate付与 サンプルプログラム
以下のプログラムを「Car.java」という名前でworkフォルダ内に保存します。Car.javaはコンパイルおよび実行は必要ありません。
1 2 3 4 5 6 7 8 |
class Car{ //フィールドにprivateを付与 private int speed ; void display(){ System.out.println("現在のスピードは" + speed + "km/hです"); } } |
Carクラスを利用する以下のプログラムを「Number29.java」という名前でworkフォルダ内に保存します。保存が完了したら、コマンドプロンプトを起動し、コンパイルおよび実行を行ってみましょう。
1 2 3 4 5 6 7 8 |
class Number29{ public static void main(String[] args){ //インスタンスを生成 Car car = new Car(); //フィールドにアクセス car.speed = 30; } } |
実行例
C:\work>javac Number29.java
Number29.java:6: エラー: speedはCarでprivateアクセスされます
car.speed = 30;
^
エラー1個
※実行例はコンパイルの例を表示しています。
フィールドにprivateを付与すると?
まず、フィールドにアクセス修飾子であるprivateを付与すると他クラスからフィールドへの代入および参照が不可になります。
Number29.java:6: エラー: speedはCarでprivateアクセスされます
car.speed = 30;
^
エラー1個
フィールドの記事ではprivateを付与していなかったので、代入および参照ができていました。しかし、クラスCarのフィールドspeedにはprivate宣言がされているため、Carクラス以外のクラスからspeedへの代入または参照ができなくなります。
Javaでは基本的にフィールドはprivateを付与します。次はなぜprivateを付与するのか、どのようにフィールドへ代入・参照を行うのか、について解説します。
なぜフィールドにprivateを付与するのか
フィールドにprivateを付与すると、代入・参照が不可になる為、デメリットがまず目につきます。privateを付与しないほうが便利にフィールドを扱うことができます。しかし、フィールドは突き詰めると単なる変数なので、データを保存する機能しかもちません。
オブジェクト指向では「モノ」をプログラミングとして落とし込み、フィールドはモノの状態を担当します。
フィールドの値はそのモノの状態として即したデータであることが求められます。
例えば、Carクラスのspeedフィールドにprivateが付与されてない場合、他のクラスからデータを代入できますが、整数であればどんなデータでも代入できてしまいます。
下記図のような車を想定してオブジェクトを作成する場合でも、スピードにマイナスの値や300以上の値を設定することも可能になります。
フィールドにprivateを付与することによって、そのフィールドに意図しない値が代入されることを防ぎ、現実世界のモノと近い役割を持つプログラムを構築できます。
フィールドへの代入及び参照を行うには
Carクラスを利用する側が代入する値に注意すればいいかもしれません。しかし、オブジェクト指向プログラミングではCarクラスを作成する際にフィールドと呼ばれる状態を表す部品は「正しい状態であることを保証」することを求められます。
例えば、speedフィールドの状態には以下のような制約を設けます。
(実際のスピードの数値に対する制約はもっと複雑になります)
- スピードは0から300までとする
- スピードはアクセルを踏んだ際に数値が上がる
- スピードはブレーキを踏んだ際に数値が下がる
- 例えば、0から100といったように数値は急激に変動せず、10ずつ上下に変動することとする
先ほども触れましたが、フィールドは単なる変数なので、フィールドのみでは上記のような制約を設けることができません。そこで、メソッドを利用してフィールドの状態を変更および取得します。フィールドにアクセスを行うメソッドのことをアクセサメソッドと呼ぶこともあります。
カプセル化のサンプルプログラムでよく出てくるメソッドを作成してみましょう。
アクセサメソッド サンプルプログラム
「Car.java」を以下のように編集(2つのメソッドを追加)し、workフォルダ内に保存します。Car.javaはコンパイルおよび実行は必要ありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
class Car{ //フィールドにprivateを付与 private int speed ; void display(){ System.out.println("現在のスピードは" + speed + "km/hです"); } //追加 speedに対するアクセサメソッド void setSpeed(int s){ speed = s; } int getSpeed(){ return speed; } } |
「Number29.java」を以下のように編集し、workフォルダ内に保存します。保存が完了したら、コマンドプロンプトを起動し、コンパイルおよび実行を行ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 |
class Number29{ public static void main(String[] args){ //インスタンスを生成 Car car = new Car(); //フィールドにアクセス //car.speed = 30; //フィールドにアクセス car.setSpeed(30); System.out.println(car.getSpeed()); } } |
実行例
C:\work>javac Number29.java
C:\work>java Number29
30
※実行例はコンパイルおよび実行までの例を表示しています。
アクセサメソッドの基本形
フィールドをprivateで宣言した場合、そのフィールドに対応するメソッドを作成しますが、以下のようなメソッドを作るのが基本です。
1 2 3 4 5 6 7 8 |
//speedフィールドにデータを代入するメソッド void setSpeed(int s){ speed = s; } //speedフィールドのデータを参照するメソッド int setSpeed(){ return speed ; } |
他クラスからフィールドにデータを代入する際には、以下のようにメソッドを作成します
戻り値 | 戻り値なし |
---|---|
メソッド名 | set + フィールド名(頭文字は大文字) |
引数 | フィールドに値を設定できる型 |
処理 | フィールドに引数から渡された値を代入する |
他クラスからフィールドのデータを参照させる際には、以下のようにメソッドを作成します
戻り値 | フィールドの値 |
---|---|
メソッド名 | get + フィールド名(頭文字は大文字) |
引数 | なし |
処理 | フィールドの値をreturnする |
フィールドの制約に即したメソッドの作成
上記で説明したアクセサメソッドはあくまでも基本形です。フィールドをprivateで宣言したからといって上記のような形で必ず作成しなければならないということではありません。
かつ、上のメソッドでは先に説明した制約が全く考慮されていないため、privateを付加する前とほとんど変わらない形になってしまいます。
もう一度、speedフィールドに対する制約例を記載します
- スピードは0から300までとする
- スピードはアクセルを踏んだ際に数値が上がる
- スピードはブレーキを踏んだ際に数値が下がる
- 例えば、0から100といったように数値は急激に変動せず、10ずつ上下に変動することとする
この制約を考慮したメソッドをCar.javaに追加します。
speedの状態を考慮したメソッド サンプルプログラム
「Car.java」を以下のように編集(メソッドを2つ追加、1つ削除)し、workフォルダ内に保存します。Car.javaはコンパイルおよび実行は必要ありません。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
class Car{ //フィールドにprivateを付与 private int speed ; void display(){ System.out.println("現在のスピードは" + speed + "km/hです"); } //追加 アクセルのメソッド void accel(){ if(speed <= 300) speed += 10; } //追加 ブレーキのメソッド void brake(){ if(speed > 0) speed -= 10; } //ゲットメソッド int getSpeed(){ return speed; } } |
「Number29.java」を以下のように編集し、workフォルダ内に保存します。保存が完了したら、コマンドプロンプトを起動し、コンパイルおよび実行を行ってみましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
class Number29{ public static void main(String[] args){ //インスタンスを生成 Car car = new Car(); //アクセルを3回踏む for(int i = 0; i < 3; i++){ car.accel(); } //ブレーキを1回踏む for(int i = 0; i < 1; i++){ car.brake(); } System.out.println(car.getSpeed()); } } |
実行例
C:\work>javac Number29.java
C:\work>java Number29
20
※実行例はコンパイルおよび実行までの例を表示しています。
補足 変更後のCar.javaの説明
speedの条件には「アクセルを踏んだ時」「ブレーキを踏んだ時」とあるので、それに準ずるメソッドを作成しています。
また、「10ずつ変動する」という条件もある為、引数からの値をフィールドに代入せず、メソッド内でフィールドの値を変動させています。
更に、アクセルはspeedの上限が300、ブレーキはspeedの下限が0になるように条件を設定しています。
private付与されたフィールドとメソッドのイメージ図
カプセル化 復習問題
- カプセル化の説明で誤りの記述を選んでください。
- 解答群
- カプセル化はフィールドへ意図しないデータを設定することを防ぐ
- カプセル化を行う際にはフィールドにpublicを付与する
- カプセル化を行う際にはフィールドにprivateを付与する
- Javaではフィールドにはprivateを付与するのが基本となる
- フィールドにprivateを付与する記述として正しい記述を選んでください。
- 解答群
private void int data;
int private data;
private int data;
void private int data;
- フィールドにprivateを付加した際、フィールドに値を設定するセッターのメソッド定義として適切だと思われるものを選んでください。
- 解答群
int setData()
int getData()
void setData(int data)
void getData(int data)
- お疲れ様でした。
まとめ
- フィールドはprivateを付与する
- 外部のクラスからフィールドにアクセスする際はメソッド経由で行う
- 外部のクラスからフィールドに値を設定する場合は引数を利用して設定する
- フィールドに値を設定する際には、必ず引数からでなく、メソッド内部で設定することもある
- メソッドはフィールドの値が意図しない値にならないように条件分岐などの処理を行う
- カプセル化を行うことにより、フィールドのデータを守る(意図しないデータを設定させない)