アクセスレベル
Javaの重要な仕組みの1つにアクセスレベルがあります。
アクセスレベルとは各クラスの独立性を高め再利用性と保守性を高める為に情報を可能な限りブラックボックス化する仕組みです。
メモリアクセスの功罪
C言語では常にメモリ上のデータを意識していましたが、これはどんな状況でも全てのデータが公開されているとも言えます。
つまり、様々な応用が利いた反面で、特定のロジックが直接には関連のないデータ(メモリ)を操作できてしまうとも言えます。
オブジェクト指向言語であるJavaではこのような問題を「可能な限り出切る事を制限する(できる)」事が特徴です。
以下のコードは前項のExample03.javaを修正したものです。
package example03; public class Example03 { public static void main(String[] args) { CabFare cabFare = new CabFare(); System.out.println("初乗り料金"); System.out.println(cabFare.fare); System.out.println("メーターがあがった!"); cabFare.incrementFare(); cabFare.fare = 580; System.out.println(cabFare.fare); System.out.println("メーターがあがった!"); cabFare.incrementFare(); cabFare.fare = 580; System.out.println(cabFare.fare); System.out.println("メーターがあがった!"); cabFare.incrementFare(); cabFare.fare = 580; System.out.println(cabFare.fare); System.out.println("メーターがあがった!"); cabFare.incrementFare(); cabFare.fare = 580; System.out.println(cabFare.fare); } }
これではお客さんが勝手に料金メーターを操作しています(CabFareクラスからは操作されていることに気づきません)。
操作しないで欲しいとCabFareクラスの説明に記述しておけば良いのでしょうが、その制限を徹底することは難しいでしょう。
このように情報が公開されているということはそれだけでリスクを追います。
この場合は情報(料金)が見えるだけであればリスクは小さくなりますが、情報(料金)を書き換えることは致命的な欠陥を生み出す可能性があるのです。
アクセスレベルは情報を隠蔽する為の仕組みです。
アクセスレベル
アクセスレベルはクラス、メソッド、コンストラクタ、インスタンス変数に適用することができます。
アクセスレベルとはそのクラス(メソッド、コンストラクタ、インスタンス変数)がどのレベルで公開されているかを表す指標であり、public, protected, package private, privateの4段階が用意されています。
これらのアクセスレベルを理解し適切に使い分けることにより、堅牢なプログラムとすることができます。
private
privateは最も制限の厳しいアクセスレベルです。
privateクラス(メソッド・コンストラクタ・インスタンス変数)は定義されたクラス内に限り公開され、外部からのアクセスからは見ることも実行することもできません。
よってソースファイルを修正する場合でも、そのクラス内での影響範囲のみを調べれば良いことになります。
尚、privateクラスはやや特殊な機能である為、現在は考えなくとも良いでしょう。
package private
package privateはprivateの制限を少し緩和し、同一パッケージからのアクセスは許可します。
つまり、同一パッケージ内で扱うに限りアクセスに制限がありません。
メソッド、インスタンス変数、コンストラクタはデフォルトではこのpackage privateとなります。
prptected
prptectedは同一パッケージまたはサブクラスでのアクセスを許可します。
詳細は継承とサブクラスを学ぶまでお待ちください。
アクセスレベルの目安
原則としてはなるべくアクセス制限を厳しくすることが推奨されます。
以下に目安を記述しますが、正しいアクセスレベルはありません。
基本的な考、インスタンス変数はprivate、メソッドは必要なもののみをpublic(またはprotected)、クラスとコンストラクタはpublicとなります。
クラス
クラスは原則としてpublicで記述します。
これはプログラムの構成単位がクラスであり、クラスの組み合わせでプログラムを構築していくオブジェクト指向言語の特徴です。
もし、そのパッケージのみで使用する局所的で限定的なクラスであればデフォルト(package private)にしますが、メソッドやインスタンス変数にアクセスレベルを制御すれば充分でありクラスはpublicであるこも多いでしょう。
コンストラクタ
コンストラクタはインスタンスの初期化を行う特殊なメソッドです。
アクセス制限を厳しくする方が良い場合もありますが、最近は周辺技術の関係でpublicとすることが推奨されいます。
インスタンスを生成しただけで複雑な処理を行うことは奨励されていない為、publicでも問題ないように設計するべきという方が正しいでしょう。
メソッド
メソッドは「必要最低限のメソッドのみをpublic」とする事が原則です。
内部処理で使用されるメソッドはprivateとして内部処理はブラックボックス化します。
他のクラスから使用されるメソッドは公開しますが、パッケージ内からのみのアクセスが想定されるのであればpackage privateとすべきでしょう。
アクセス修飾子
アクセス修飾子には、public, protected, privateの3つが予約語として用意されており、package privateは省略した場合に適用されるアクセスレベルです。
アクセスレベルを意識してCabFare.javaを書き換えると次のようになります。
[CabFare.java]
package example03; public class CabFare { // 料金 private int fare = 580; // コンストラクタ public CabFare() { } // 料金加算メソッド public void incrementFare() { fare = fare + 80; } }
コードを修正して保存するとExample02でコンパイルエラーが発生すると思います。
コンパイルエラーが発生すると、タスクにエラーメッセージが表示されます。
これで、勝手に料金が変更されることはなくなりましたね。
※尚、メソッドのアクセスレベルはpackage private(アクセス修飾子なし)でも問題はありません。
アクセサメソッド
fareをprivateにする事でfareを書き換えることはできなくなりましたが、fareの値を参照することもできなくなりました。
これでは困りますので、fareの値を取得するというメソッドを追加します。
以下のコードを追加しましょう。
// 料金取得 public int getFare() { return fare; }
このようにメンバ変数を直接参照させずに、メソッドを介してアクセスさせるメソッドをアクセサメソッドと呼びます。
取得の為のアクセサメソッドは頭にgetと記述してメンバ変数名を続け、getterメソッドと呼ばれます。
また、これに対して値を格納する為のメソッドはsetterと呼ばれますが、今回は料金を設定されたくはないのでgetterのみを記述するべきでしょう。
状況によりsetterのみ、getterのみ、両方記述する、両方記述しないを使い分ける事により、privateな変数のアクセスレベルを自由に調整できると言えます。
setterを定義するのであれば次のようになります。
// 料金設定 public void setFare(int value) { fare = value; }
一般的にアクセサメソッドとメンバ変数はセットで定義され、次のようになります。
// インスタンス変数 private 型 変数; // 取得 public 型 get変数名() { return 変数; } // 設定 public void set変数名(型 value) { 変数 = value; }
では、最終的なコードを確認しておきましょう。
[CabFare.java]
package example03; // タクシー料金クラス public class CabFare { // 料金 private int fare = 580; // コンストラクタ public CabFare() { } // 料金加算メソッド public void incrementFare() { fare = fare + 80; } // 料金取得 public int getFare() { return fare; } }
package example03; public class Example03 { { public static void main(String[] args) { CabFare cabFare = new CabFare(); System.out.println("初乗り料金"); System.out.println(cabFare.getFare()); System.out.println("メーターがあがった!"); cabFare.incrementFare(); System.out.println(cabFare.getFare()); System.out.println("メーターがあがった!"); cabFare.incrementFare(); System.out.println(cabFare.getFare()); System.out.println("メーターがあがった!"); cabFare.incrementFare(); System.out.println(cabFare.getFare()); System.out.println("メーターがあがった!"); cabFare.incrementFare(); System.out.println(cabFare.getFare()); } }