勉強中 Javaでオブジェクト指向プログラミング Vol.2
クラスについて。 構成要素の説明を中心に大雑把に。 急に出てくる用語も記事内で解説してるので、二度読めば分かるかもしれません。 これは自分に言ってます。
クラスとは
クラスとは、オブジェクトの持つデータ、そしてオブジェクトが行う操作(メソッド)を定義する仕組みのこと。 前回の記事でも書いた通り、クラス自体も「クラスオブジェクト」というオブジェクトであり、ここで定義するデータやメソッドは「インスタンスオブジェクト」に対しての定義と考えるとスマートに理解出来ると思います。
クラスオブジェクトに記された設計図(オブジェクトの定義)を元にして、インスタンスオブジェクトを作成することができます。 一つのクラスオブジェクトから、いくつでもインスタンスを作成できます。
インスタンスに関しては今度別の記事で。
メンバ
メンバとは、クラスを構成する要素のこと。 メンバは、サブクラスに継承できる必要がある。
次のようなものをメンバという。
- フィールド(メンバ変数とも)
- メソッド
- 内部クラス(インターフェース含む)
また、次のようなものは含まない。
- コンストラクタ
- インスタンス初期子
- static初期子
識別子
識別子とは、ローカル変数、フィールド、メソッド、クラスに与える名前のこと。 文字通り。
単純に例を言えば、変数名が識別子である。
修飾子
修飾子とは、識別子の性質を決めるキーワードのこと。 種類が多いので、全体の紹介は省く。
よく目にする例としては、「public」や「private」などのアクセス修飾子。
カプセル化
カプセル化は、オブジェクト指向プログラミングの概念の一つ。 簡単に言えば、直接的にオブジェクトのフィールドにアクセス(値の変更や参照)することを禁止して、変更用・参照用に用意したオブジェクト内のメソッドを介してフィールドの値を変更・参照すること。 これにより、オブジェクト内のデータとメソッドを完結に纏めることができる。
変更用のメソッドを「Setter」、参照用のメソッドを「Getter」などと言い、この二つのメソッドを「アクセサ」という。 また、制限することを「隠蔽」などと言ったりもする。
カプセル化をすると、オブジェクト内のフィールドに直接アクセスすることを制限され、メソッドを介さなければならないため、一見不便に見える。 しかし、カプセル化を行うことによって得られる利点も多い。
最初は「メソッドを介す」ということが煩雑に思ったが、次のような例え話を自分で考えて自分で納得した。
今、自分の手元にはマウスがある。 マウスはクラスで、右ボタン・左ボタン・ホイールの3つはメソッドである。 もしもマウスの状態(フィールド)の操作をメソッドを介さずに行っているとすれば、基板にポツンと生えているだけの押しボタンスイッチをチマチマと押したり(フィールドへの直接的なアクセス)、ホイールのつもりの上下押しボタンスイッチをこれまたチマチマと連打する必要がありそうだ。 しかも、その基盤はマウスと違って触り心地は最悪だ。 メソッドを介すことで、煩雑な操作を行わなくとも簡単にマウスを動かすことができる。
カプセル化されたメソッドは、内部は見せない。 カプセルの中のみで処理を完結させるべきであり、外部からの直接的な操作は不具合に繋がったり、扱いが煩雑になるため避けるべきである。
これが大まかなカプセル化の考え方。 自分も完璧に理解してるわけじゃないので、自分の考えもちょっと混ぜてる。
本質的には、カプセル化は「隠蔽する」ということを指してるわけではない気がする。
ユーザーによる入力の際、不自然な操作が行われないようにする必要がある。 例えば、年齢ならマイナスの値などは弾かれるべきである。 例えば、次のようなコードがある。
class Hoge { static int fuga; } public class Main { public static void main(String[] args) { Hoge.fuga = -10; System.out.println(Hoge.fuga); // -10 } }
これは-10という値がfugaに代入されてしまう。 そこで、フィールドをカプセル化し、Setterで0未満の値をブロックしてみる。
class Hoge { private int fuga; public int getter() { return this.fuga; } public void setter(int num) { if(num>=0) { this.fuga = num; } else { System.out.println("不正な値"); } } } public class Main { public static void main(String[] args) { Hoge hoge = new Hoge(); // Hogeを元に、hogeインスタンスを作成 hoge.setter(-10); System.out.println(hoge.getter()); // 代入されずに0が表示される } }
フィールドを直接操作した場合は想定外の値が代入されても防げないが、Setterを介することで想定外の値の代入を防ぐことが出来る。 これにより、プログラム利用者の間違った利用によるエラーの発生を最小限に留めることができる。
またカプセル化をすると、仕様変更にも比較的簡単に対応出来る。 ここで例を書こうと思ったものの、まだJavaを利用して成果物を作ったことがないので、例が思い付かなかった。
ただ、オブジェクトの操作を全てオブジェクト内で完結させているイメージを浮かべると、なんとなく仕様変更に強そうな感じがする。 あまりにも投げやりなので後々何かの機会にカプセル化について再度考えてみることにする。 こうやって話の〆を無理矢理にする自分を後で見返して戒めにするための一文だと感じる。 頑張ります。
アクセス修飾子
カプセル化は、アクセス修飾子をフィールドに付与することから始まる。 アクセス修飾子は次の通り。
- public
- protected
- なし(省略)
- private
publicを設定すると、全てのクラスからアクセスが可能になる。
protectedを設定すると、現在のクラスとサブクラスからアクセスが可能になる。
アクセス修飾子を省略すると、現在のクラスが属するパッケージのクラスからアクセスが可能になる。
privateを設定すると、現在のクラスからのみアクセスが可能になる。
これらのアクセス修飾子を適切に設定し、メソッドを介してフィールドの変更・参照を行う。
実際に設定してみると、次のような感じ。
public int Pub; protected int Pro; int Pac; private int Priv;
フィールド(メンバ変数)
Javaでは、オブジェクトが持つデータのことを「フィールド」と呼ぶ。
「メンバ変数」と呼ばれたり、インスタンスごとに固有に割り当てられるので「インスタンス変数」または「インスタンスフィールド」とも呼ばれる。
フィールドは、クラスの状態を定義する役割を持つ。 他のメソッドやコンストラクタからも参照可能。
オブジェクトが持つデータを変数としてクラス内に記述すると、それがフィールドと呼ばれるという話です。
例えば学生だとしたら次のようなフィールドを定義する必要がありそうですね。
class student { String name // 名前 int studentNumber // 学籍番号 }
ローカル変数
メソッドやコンストラクタの状態を定義する変数のことを「ローカル変数」と呼ぶ。 クラス内にあるためメンバ変数のようにも見えるが、クラスの状態を定義するものではないためメンバ変数とは異なる。
ローカル変数はメンバ変数とは違い、同一のメソッドまたはコンストラクタ内でのみ参照できる。
class student { // メンバ変数 int member = 10; void methodA() { // ローカル変数 int localA = 20; // メンバ変数は参照可能 System.out.println(member); // methodAのローカル変数なので参照可能 System.out.println(localA); // methodBのローカル変数なので参照不可 System.out.println(localB); } void methodB() { // ローカル変数 int localB = 30; } }
クラス変数(静的メンバ変数)
同一クラスやサブクラスで共有される変数のことを「クラス変数」または「クラスフィールド」という。
インスタンス変数(フィールドの項)に対して「静的メンバ変数」と呼ばれたり、「静的フィールド」などと呼ばれる。 厳密には違うが、グローバル変数のような使い方が出来ると言われがち。
クラスから生成されたインスタンスオブジェクトでも共有されている。
クラス変数を使用するには、形名の前にstatic修飾子を記述する。
class Hoge { static int foo = 100; } public class Main { public static void main(String[] args) { System.out.println(Hoge.foo); // 100 Hoge fuga = new Hoge(); // fugaインスタンスを生成 fuga.foo = 200; // fugaからクラス変数に代入 System.out.println(Hoge.foo); // 200 Hoge piyo = new Hoge(); // piyoインスタンスを生成 piyo.foo = 300; // piyoからクラス変数に代入 System.out.println(Hoge.foo); // 300 } }
メソッド
オブジェクトに属するサブルーチン(ひとまとまりの処理)を「メソッド」という。
メソッドは呼び出して使う。 普通、メソッドは機能ごとに分割して作成する。
次の例は、自身が属するクラスのフィールドに格納されている値を返すメソッド。
class Hoge { static int fuga = 100; // fugaを返すメソッド static int foo() { return Hoge.fuga; } } public class Main { public static void main(String[] args) { System.out.println(Hoge.foo()); } }