こんにちは。ECF Techブログ
担当 Michiharu.Tです。
Javaプログラミング入門記事の第11章をお送りいたします。
第11章のテーマは「アクセス修飾子とカプセル化」です。
本連載の初回および章立ての一覧については下記のリンクから確認できます。
解説動画
2024/02/16 解説動画を掲載しました。
11-1 プログラムの安全性
オブジェクト指向プログラミングの目指す重要なテーマのひとつに、プログラムの安全性があります。ここではプログラムの安全性について、9章で登場したPointCardクラスを使って考察したいと思います。
プログラム例(PointCard.java 再掲)
public class PointCard { int id; //カード番号 int point; //残ポイント //コンストラクタ PointCard(int id, int point) { this.id = id; this.point = point; } //支払処理 void payment( int price ){ //金額の1%をポイントに加算 point += price*0.01; } }
一見すると問題ないプログラムに見えますが、ポイントカードの仕様が次のようになっている場合、このプログラムが問題になる場合があります。
ポイントカードの仕様
- IDは1度割り振られたあとは変更されない。
- ポイントが0未満の値になることはない。
お店アプリを製作する中で、PointCardクラスが様々な別クラスから利用される場合、次のような誤った使い方をされてしまう場合があります。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ PointCard card = new PointCard(1,100); card.point = -200; card.id = 5; } }
PointCardクラスからインスタンスを生成し、利用するプログラムですが次のような問題があります。
- pointフィールドに負の数を代入してしまっている(
card.point = -200
)。 - idフィールドが後から変更されてしまっている(
card.id = 5
)。
このように、フィールドが本来あってはならない値になってしまう可能性がある。という点では、PointCardクラスは安全なプログラムとは言えません。本章では、このPointCardクラスの修正をとおして、クラスの安全性を高めるプログラミングについて見ていきます。
11-2 アクセス修飾子
PointCardクラスを修正する最初の一歩として、まず始めにフィールドを他のクラスからアクセスできないようにしたいと思います。アクセス修飾子を使うことで、これを実現できます。
11-2-1 アクセス修飾子の基本
アクセス修飾子は修飾子のひとつで、クラス、フィールド、メソッドなどに付け加えることができます。アクセス修飾子を使用することで、フィールドやメソッドの公開範囲を定義することができます。
アクセス修飾子にはprivate
、protected
、public
の3種類があり、アクセス修飾子そのものを付けない方法も加え、4種類の公開範囲が定義できるようになっています。
下表にアクセス修飾子とその公開範囲を示します。下に行くほど公開可能な範囲が広くなります。
アクセス修飾子 | 公開範囲 |
---|---|
private | 自身のクラス内のみでアクセス可能 |
※指定なし | 自身が属するパッケージ内のクラスからはアクセス可能 |
protected | 自身の属するパッケージ内のクラスと自身のサブクラスからアクセス可能 |
public | すべてのクラスからアクセス可能 |
※表内のパッケージやサブクラスの概念は、後の章の学習内容になります。現時点ではpublicとprivateについてご確認頂ければOKです。
11-2-2 アクセス修飾子の動作
では実際に、アクセス修飾子を付け加えたプログラムがどのように動作するかを確認してみましょう。
プログラム例(PointCard.java)
public class PointCard { public int id; //カード番号 private int point; //残ポイント //コンストラクタ PointCard(int id, int point) { this.id = id; this.point = point; } //支払処理 void payment( int price ){ //金額の1%をポイントに加算 point += price*0.01; } }
元のPointCard.javaとの変更点は、2,3行目だけです。public
とprivate
をそれぞれのフィールドにつけ加えています。つづけて、メインクラスのプログラムです。
プログラム例(Main.java)
public class Main{ public static void main(String[] args){ PointCard card = new PointCard(1,100); System.out.println(card.id); System.out.println(card.point); } }
コンパイル結果
Main.java:5: エラー: pointはPointCardでprivateアクセスされます System.out.println(card.point); ^ エラー1個
PointCardクラスにおいて、idフィールドの宣言部分にはpublic
が付けられています。public
が付けられているフィールドはどのクラスからでもアクセスできるため、Main.javaの4行目の記述はエラーになりません。
一方で、pointフィールドは宣言部分にprivate
が付けられているため、別のクラス(ここではMainクラス)からのアクセスができません。したがって5行目でエラーが発生しています。
2つのクラス間のアクセス可否の状態を示すと下図のようになります。
11-3 アクセサメソッド
アクセス修飾子を使って、フィールドを他のクラスからアクセスさせない状態を作ることができましたが、一方で、フィールドに対する一切のアクセスができない状態は大変不便です。次はアクセサ(Accessor)メソッドを使って、この点を解消するプログラムを作成しましょう。
11-3-1 アクセサメソッドの定義
アクセサメソッドはフィールドへのアクセス手段を提供するメソッドです。といっても特別な文法があるわけではなく普通のメソッドです。
アクセサメソッドには次の2種類が存在し、書き方には一定のルールがあります。
- ゲッターメソッド ※ゲッターとも言います
- セッターメソッド ※セッターとも言います
(1)ゲッター(getter)メソッド
フィールドの値を返すメソッドです。メソッド定義は次のように記述します。
public 戻り値型 getXxxx(){ return フィールド名; }
- 戻り値型は、フィールドの型と同じになります。
Xxxx
の部分にはフィールド名の先頭1文字を大文字にした名前を設定します。- アクセス修飾子は
public
とします。
PointCardクラスのpointフィールドに値を設定するゲッターは次のように定義します。
public int getPoint(){ return point; }
(2)セッター(setter)メソッド
フィールドに値をセットするメソッドです。メソッド定義は次のように記述します。
public void setXxxx(型名 引数名){ this.フィールド名 = 引数名; }
- 型名はフィールドの型と同じになります。
- 引数名とフィールド名は同じになります。
Xxxx
の部分にはフィールド名の先頭1文字を大文字にした名前を設定します。- アクセス修飾子は
public
とします。
PointCardクラスのpointフィールドに値を設定するセッターは次のように定義します。
public void setPoint(int point){ this.point = point; }
11-3-2 アクセサメソッドの使用例
アクセサメソッドを使って、PointCardクラスを改善したプログラム例を示します。
プログラム例(PointCard.java)
public class PointCard { private int id; //カード番号 private int point; //残ポイント PointCard( int id, int point ){ this.id = id; this.point = point; } //idフィールドのゲッター public int getId(){ return this.id; } //pointフィールドのゲッター public int getPoint(){ return this.point; } //pointフィールドのセッター public void setPoint( int point ){ if( point >= 0 ){ this.point = point; } } //支払処理 void payment( int price ){ //金額の1%をポイントに加算 point += price*0.01; } }
- フィールドはすべて
private
としています。 - どちらのフィールドも値を取得する可能性があることを前提に、ゲッターを定義しています。
- idフィールドはインスタンス生成(コンストラクタ呼び出し)時に設定されたら、以降変更されない仕様なので、セッターは定義していません。
- pointフィールドは他クラスから値を設定する場合があるためセッターを定義しています。ただし、負の値はフィールドに代入されないようになっています。
次に、修正したPointCardクラスを利用するメインクラスの例を示します。
プログラム例(Main.java)
public class Main { public static void main(String[] args) { PointCard card = new PointCard(1,100); //pointを-50に変更 card.setPoint(-50); System.out.println(card.getPoint()); //pointを200に変更 card.setPoint(200); //idとpointを表示 System.out.println(card.getId()); System.out.println(card.getPoint()); } }
実行結果
100 1 200
解説
3行目:PointCard card = new PointCard(1,100);
PointCardインスタンスを生成しています。コンストラクタ経由でidフィールドに1
、pointフィールドに100
が代入されます。
5行目:card.setPoint(-50);
セッターを使って、pointフィールドに-50
の代入を試みています。setPointメソッドはpoint >= 0
の場合のみ、値がフィールドに設定されるようになっているため、-50
は設定されません。
6行目:System.out.println(card.getPoint());
PointCardインスタンスのpointフィールドの値を取得し、表示しています。フィールドはprivateになっているため、このようにゲッターを使用する必要があります。実行結果から、pointフィールドは100
のままであることがわかります。
8行目:card.setPoint(200);
セッターを使って、pointフィールドに200
を設定しています。こちらは正の数なのでフィールドに設定されます。
10,11行目
PointCardインスタンスの2つのフィールドを表示しています。実行結果からpointフィールドが200
になっていることがわかります。
これで、PointCardクラスが誤用されにくい、安全性の高いクラスとなりました。
11-4 カプセル化
本章ではここまで、PointCardクラスの安全性向上のための改善を進めてきました。この一連の修正は、オブジェクト指向プログラミングにおけるカプセル化という概念の実現を目指したものです。
カプセル化とは、簡単に言えばデータとデータに対する操作をひとまとめにすることです。Javaのプログラムにおいては、インスタンスがカプセルにあたると言えます。
さらに、データを外部から見えなくしてしまう(隠ぺい)ことで、保守性や安全性を高められるとする考え方です。下図はカプセル化のイメージです。
今回のPointCardクラスのように、原則としてデータ(フィールド)をprivate
とし、外部のプログラム(他のクラスなど)からアクセスをする場合はメソッドを利用してもらう設計は、カプセル化を実現する基本的なパターンとなります。
[PR]
Javaをもっと詳しく学びたい!という方には、下の書籍がおススメです!筆者も愛用しています。