12.一方通行と飛び出し禁止
~メソッドあれこれ~

 プログラミングでは、なんでうまくいかないのぉ、こんなはずじゃなかったのにぃ...と泣いたりイラついたりすることが多いはず。 もしかするとこの章を理解していると、そんなことも少しは減るのではないかしら。

長方形よ変身せよ!

 またまた長方形の例で失礼します。今回は長方形を変身させるメソッドを作ってみましょう。

 変身といっても図にあるように、辺の長さだけを変えるのです。 またどんな大きさにするかは、引数で与えられているとしましょう。 つまりグレイの長方形を、白い長方形と同じ大きさにするメソッドを考えるのです。



 まずはメソッドmainから。

public class Main {
 public static void main(String[] args) {
  MyRectangle a = new MyRectangle(3.0, 4.0);
  MyRectangle b = new MyRectangle(10.0, 20.0);

  a.changeA(b.getWidth(), b.getHeight());
  System.out.println(
   "a width:" + a.getWidth()
   + " height:" + a.getHeight());
  System.out.println(
   "b width:" + b.getWidth()
   + " height:" + b.getHeight());
 }
}

 グレイの長方形をaとします。 メソッドchangeAで、実引数に長方形bの各辺の長さを指定します。出力は次のようになります。

a width:10.0 height:20.0
b width:10.0 height:20.0

 どちらの長方形も10×20になっていますね。

 ではクラスMyRectangleにchangeAを追加してみましょう。 他にもいろいろメソッドはありましたが、この際関係ないので省略しました。

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 double getHeight() {
  return this.height;
 }

 void changeA(double argW, double argH) {
  this.width = argW;
  this.height = argH;
 }

}

 まあ実は今までに出てきてたメソッドsetSizeと同じなのですけどね。 ここで実引数と仮引数のデータの受け渡しを復習しておきましょう。



 メソッドchangeAが呼び出されると、実引数b.getWidth()の値、つまりb.widthの値が、対応する仮引数argWに代入されます。 argHについても同様です。 次にその値が、メソッド内でa.widthとa.heightに代入されます。 これで長方形aもbも10×20になりました。

 ここで注目してほしいのは、赤い矢印が一方通行だということです。

 メソッドが呼び出されると、実引数の値が仮引数に代入されます。 赤い矢印の方向ですね。ところがメソッドの処理が終わっても、仮引数の値は実引数には戻ってきません。 (まあ実引数には式も指定できるので、戻ってこられても困るんですけどね) メソッド側から呼び出し元へ情報を渡したければ、return文で戻り値を指定してやればよいのです。

どっちも変身

 では実引数に長方形そのものを指定して、同じことをやってみましょう。次のようなmainになります。

public class Main {
 public static void main(String[] args) {
  MyRectangle a = new MyRectangle(3.0, 4.0);
  MyRectangle b = new MyRectangle(10.0, 20.0);

  a.changeB(b);
  System.out.println(
   "a width:" + a.getWidth()
   + " height:" + a.getHeight());
  System.out.println(
   "b width:" + b.getWidth()
   + " height:" + b.getHeight());
 }
}

 クラスMyRectangle中のメソッドchangeBは次のようになります。 public class MyRectangle {
 private double width;
 private double height;
   :
 void changeB(MyRectangle rect) {
  this.width = rect.width;
  this.height = rect.height;
 }

}

 仮引数にMyRectangle型の変数を使用していることに注意してください。データの動きは次のようになります。



 実引数bの値は、10×20の長方形のオブジェクトの参照が入っています。 この値が仮引数rectに渡されるのですから、rectとbは同じ長方形を指すことになります。 rect.widthは10、rect.heightは20なので、この値をインスタンス変数に代入すればよいですね。

 ではメソッドchangeBを次のように書き換えるとどうなるでしょうか。

public class MyRectangle {
 private double width;
 private double height;
   :
  void changeB(MyRectangle rect){
  double w, h;
  w = this.width;
  h = this.height;
  this.width = rect.width;
  this.height = rect.height;
  rect.width = w;
  rect.height = h;
 }
}

 これはメソッド内で、this.widthの値をrect.widthの値と入れ替えています。 this.heightとrect.heightについても同様です。 代入すると前の値は完全に消えてなくなってしまうので、変数wやhを退避用に利用して入れ替えをおこなっています。
 このようにメソッド内で宣言し使用する変数を局所変数といいます。入れ替えの結果どうなるのか次の図を見てください。



 入れ替えの結果、長方形bは3×4になっています。出力は次のようになります。

a width:10.0 height:20.0
b width:3.0 height:4.0

 もう一度確認しましょう。 赤い矢印のように、実引数の値は仮引数に代入されます。 この値が参照であったために、bの指し示すオブジェクトをメソッド内で直接いじることができたわけです。

あなたはだぁれ?

 仮引数や局所変数は、メソッド内でのみ使えます。 コンストラクタもメソッドと同様です。

 次はMyRectangleのコンストラクタで仮引数にインスタンス変数と同じ名前を使った例です。
 コンストラクタ内での名前width(赤下線)は、仮引数widthのことになります。 もしこのメソッド内でインスタンス変数widthを使いたければ、頭に「this.」をつけてやらなければなりません。
 メソッドgetWidthのように仮引数にも局所変数にもwidthが使われていない場合、名前widthはインスタンス変数widthを表します。



飛び出し注意

 次のメソッドは、配列aの中の0が何番目(0番目から始まる)に現れるかを調べています。 3が出力されます。問題ないですね。

public void m() {
 int[] a = { 3, 2, 4, 0, 1, 2 };
 int i;
 for (i = 0; i < a.length; i++) {
  if (a[i] == 0) {
   break;
  }
 }
 System.out.println(i);
}

 次はコンパイルエラーになってしまいます。さてどこがちがうのでしょうか。

public void m() {
 int[] a = {3, 2, 4, 0, 1, 2};
 for(int i = 0; i < a.length; i++) {
  if(a[i] == 0) {
   break;
  }
 }
 System.out.println(i);
}

 そう、変数iの宣言の位置が違います。 このようにfor文の繰り返しで使うカウンタのような役目の変数は、for文の最初で宣言することができます。 でもその変数は繰り返し部分でしか使えないので、breakで飛び出した先で出力しようとするとエラーになってしまいます。



 次のように、変数iを繰り返しの前で宣言しておけば、エラーにならず3が出力されます。

 public void m() {
  int[] a = { 3, 2, 4, 0, 1, 2 };
  int i;
  for (i = 0; i < a.length; i++) {
   if (a[i] == 0) {
    break;
   }
  }
  System.out.println(i);
 }
}

いきなり使うと

 変数には、インスタンス変数の他に、メソッド内で使う局所変数がありました。 何も値をいれずにいきなり使うとどうなるかを見てみましょう。 (仮引数は実引数の値が代入されるのでここでは問題にしません)

  public class DefaultValueTest {
 private int a;

 public void test() {
  int b;
  System.out.println(a);
   //0が出力される
  System.out.println(b);
   //コンパイルエラー
 }
}

 上にあるように、インスタンス変数は何も値を入れずに、いきなり使っても大丈夫です。 なにしろ自動的に初期値が入っていますから。
 変数の型によって初期値の値は異なりますが、0か0に相当する値になります。 これは7章でやった、配列生成時の配列要素の初期値と同じになります。

数値型(byte、short、int、long、float、double) は 0
boolean は false
char は \u0000(すべて0のUnicode値)
参照型 は null

 初期値は0でいいや、という場合も、きちんとプログラム中で自分で0を与えておく方がよいです。上では

   private int a = 0;

ですね。

 一方局所変数はいきなり使うことができません。 上のbのように何も与えずに出力しようとすると、コンパイルエラーになります。 きちんと何か入れてから使ってください。