10.よそ村の男に気をつけろ
~アクセス修飾子~

 さあ、いよいよオブジェクト指向っぽくなってきますよ。 オブジェクト指向の、情報のカプセル化ということを実現するために欠かせないアクセス修飾子をやりましょう。

憂慮すべき事態

 さあ、復習です。次のプログラムで何が出力されるでしょうか?①②に注目してください。

[ファイルMyRectangle.java]
package shape;
public class MyRectangle {
 double width;
 double height;

 MyRectangle(double w, double h) {
  this.width = w;
  this.height = h;
 }

 double getArea() {
  return this.width * this.height;
 }

 void setSize(double w, double h) {
  this.width = w;
  this.height = h;
 }
}

[ファイルMainA.java]
package shape;
public class MainA {
 public static void main(String[] args) {
  MyRectangle a = new MyRectangle(3.0, 4.0);
  a.width = -3.5;//①
  a.height = 2.0;//②
  System.out.println("Area:" + a.getArea());
 }
}

 出力は

Area:-7.0

 なんと面積がマイナスで出てきてしまいます。これはどうしたことでしょう。

 実は①で長方形の幅としてはありえない-3.5が、長方形aのwidthに代入されているからです。
 このように直接インスタンス変数にエラーデータを代入されては、たまったもんではありません。 お咎めなしでストンと入ってしまうのですから。



 もしメソッドsetSizeを使って大きさを変えている場合は、メソッド側で仮引数の値をチェックすることで、難を免れることができます。

[ファイルMyRectangle.java]
package shape;
public class MyRectangle {
 double width;
 double height;

 MyRectangle(double w, double h) {
  if (w < 0) {
   w = 0;
  }
  if (h < 0) {
   h = 0;
  }

  this.width = w;
  this.height = h;
 }

 double getArea() {
  return this.width * this.height;
 }

 void setSize(double w, double h) {
  if (w < 0) {
   w = 0;
  }
  if (h < 0) {
   h = 0;
  }

  this.width = w;
  this.height = h;
 }
}

[ファイルMainA]
package shape;
public class MainA {
 public static void main(String[] args) {
  MyRectangle a = new MyRectangle(3.0, 4.0);
  a.setSize(-3.5, 2.0); //①´
  System.out.println("Area:" + a.getArea());
 }
}

 ①´でエラーデータが実引数として渡されても、メソッドsetSizeの太字の部分でチェックしています。 エラーデータが入ってきたときの対処はいろいろありますが、ここでは簡単に0にしてしまいます。 コンストラクタでもエラーチェックをすることにしました。面積は0.0で出力されます。



ガードは固いの

 メソッドを使った場合は、上のようにメソッド側でチェックを入れればいいのですが、冒頭のように直接代入されてしまうとどうにもなりません。 インスタンス変数を悪い輩から守る方法をお教えします。

 まずガチガチに守るには、宣言の頭に「private」を付けます。

private double width;

他のクラスから

a.width = -3.5;

のようにすると、コンパイラに怒られます。つまりインスタンス変数widthは、同じクラス内のメソッドの中で

this.width

の形でしか使えません。 (実はこの「this」は省略可能です) (さらに実はこれはちといい過ぎ。同じクラスのオブジェクトのインスタンス変数についてはprivateでもアクセスできます。 でもややこしくなるのでここではカット。)



 これはちょっとやりすぎ、同じパッケージであれば他のクラスからも使えるようにしてよ、 という場合は、宣言の頭に何も付けません。今まではこのやり方でした。他のパッケージのクラスからは使えません。

double width;



 さらにもうどっからでも来い、という場合は、宣言の頭に「public」を付けます。他のパッケージのクラスからでも使えます。

public double width;



 以上を一つにまとめてみました。かえってわかりにくいかな。



 publicやprivateをアクセス修飾子といいます。次に表でまとめてみましょう。

privateクラス内からのみアクセス可能
なしクラス内および同一パッケージのクラス内からアクセス可能
publicどこからでもアクセス可能


 privateはおうちから出てはいけない箱入り娘、指定なしは村中の男とはしゃべってもいいほどほど娘、 publicはよそ村の男でも平気なおてんば娘、といわけ。余計混乱してしまった人はごめんなさい。



勝手に呼び出さないで

 さてインスタンス変数を守る方法はわかりました。次はメソッドを守る方法を見ましょう。

 メソッドsetSizeでは長方形の大きさを変えられます。でも勝手に変えてほしくない場合もあるはずです。 そんなときはメソッドの宣言で、インスタンス変数と同じアクセス修飾子を指定します。 メソッドのアクセス修飾子も、インスタンス変数のときと同じです。



 private指定のメソッドは、クラス内でのみ呼び出し可能になります。
 次のメソッドsetDoubleSizeは幅と高さを、実引数の2倍の長さにします。 この中でメソッドsetSizeを呼び出しています。頭に「this.」が付いていますが、これは省略できます。
 このように同じクラスの中のメソッドが利用するだけで、他のクラスからは呼び出されないと判っている場合は「private」を指定しましょう。

[ファイルMyRectangle.java]
package shape;
public class MyRectangle {
 private double width;
 private double height;

 略
 public void setDoubleSize(double w, double h) {
  this.setSize(2 * w, 2 * h);
 }

 private void setSize(double w, double h) {
  if (w < 0) {
   w = 0;
  }
  if (h < 0) {
   h = 0;
  }
  this.width = w;
  this.height = h;
 }
}

 なお、コンストラクタのアクセス指定も、メソッドとまったく同じです。

下駄と雪駄?

 一般的にインスタンス変数のアクセス修飾子はprivateにして、直接他のクラスから見られたり変えられたりしないようにします。
 しかし「見たい!」「変えたい!」という場合もあるでしょう。 そのために用意するメソッドを、getter(ゲッター)、setter(セッター)、併せてaccessor(アクセサ)といいます。 getterはインスタンス変数の値を戻り値とするメソッドで、setterは実引数の値をインスタンス変数に代入するメソッドです。

 次のような決まりを守ってメソッド名をつけておくと、いろいろな場所でそのクラスを利用できる可能性がでてきます。

 getter get+インスタンス変数の先頭1文字を大文字にしたもの
 setter set+インスタンス変数の先頭1文字を大文字にしたもの

 この決まりでクラスMyRectangleを書き直してみました。もう一度見直しておきましょう。

package shape;
public class MyRectangle {
 private double width;
 private double height;

 public MyRectangle(double w, double h) {
  if (w < 0) {
   w = 0;
  }
  if (h < 0) {
   h = 0;
  }
  this.width = w;
  this.height = h;
 }

 public double getWidth() {
  return this.width;
 }

 public void setWidth(double w) {
  if (w < 0) {
   w = 0;
  }
  this.width = w;
 }

 public double getHeight() {
  return this.height;
 }

 public void setHeight(double h) {
  if (h < 0) {
   h = 0;
  }
  this.height = h;
 }


 public double getArea() {
  return this.width * this.height;
 }

 public void setSize(double w, double h) {
  if (w < 0) {
   w = 0;
  }
  if (h < 0) {
   h = 0;
  }
  this.width = w;
  this.height = h;
 }
}

粉薬とカプセル

 オブジェクト指向の考え方に、情報のカプセル化があります。 ある情報について、その中身をざらざら人目にさらしておくのでなく、カプセルに閉じ込めてゴクンと一飲みできるようにしましょう、というわけです。 Javaではクラスのカプセル化が重要になります。

 インスタンス変数という、オブジェクトの持つ大事なデータを出しっぱなしにしないために、privateの指定をしなさい。

 オブジェクトについての情報を得たり、何かやらせたいときは、そのオブジェクトのメソッドの呼び出しで実現できるようにしなさい、というわけです。 そのためのメソッドはpublic指定にします。

 getHeightやgetWidth、getAreaなどです。public指定のメソッドは最低限にして、クラス内部の仕様の変更は、 外部にできるだけ影響しないようにしましょう、ということです。

 「着替えさせてー」「食べさせてー」「あのおもちゃがないー」甘えん坊には親も振り回されます。 でもよく考えれば、親の育て方にもよるのでしょうね。自分なりに考え、さっさと一人で着替えて食事して遊んで、なんていうときは、 もうちょっと甘えてよ、などと考えてしまう。親も身勝手ですね。  自分のことは自分でするしつけの行き届いたクラスは、カプセル化が実現できているのでしょう。決して甘えん坊で手の掛かるクラスを作らないようにしたいものです。