メソッド (1)

メソッド

メソッドを使うといくつかの命令の列を束ねて、一つの命令として扱うことができる。

次のプログラムでは、sayHelloというメソッドを作って起動している。sayHello というメソッドは、起動されると

Hello!
This is sayHello.

と、表示するだけの簡単なメソッドである。

public class SayHello {

  public static void main(String[] args) {
    sayHello();
  }

  public static void sayHello() {
    System.out.println("Hello!");
    System.out.println("This is sayHello.");
  }
}

上記の例では、次の2つの命令を束ねて、sayHello() という一つの命令に置き換えている。

System.out.println("Hello!");
System.out.println("This is sayHello.");

この例では、以下のように sayHello() と一回だけ起動しているだけであった。

public static void main(String[] args) {
  sayHello();
}

そのため、以下のように表示される。

Hello!
This is sayHello.

ここで、下記のように sayHello(); と3回書くことによって、sayHelloという一連の処理を3回連続で起動することができる。

public static void main(String[] args) {
  sayHello();
  sayHello();
  sayHello();
}

これを実行すると、次のように表示される。

Hello!
This is sayHello.
Hello!
This is sayHello.
Hello!
This is sayHello.

つまり、簡単に置き換えると、以下のような処理をしていることになる。

public static void main(String[] args) {
  // sayHello();
  System.out.println("Hello!");
  System.out.println("This is sayHello.");

  // sayHello();
  System.out.println("Hello!");
  System.out.println("This is sayHello.");

  // sayHello();
  System.out.println("Hello!");
  System.out.println("This is sayHello.");
}

このように、メソッドを宣言することによって、以下のような利点を得られる可能性がある。

メソッドの宣言 (定義)

最も単純なメソッドの宣言 (定義) 方法は以下の通りである。

public static void <メソッドの名前> () {
  <メソッド本体>
}

ここで、<メソッドの名前>には覚えやすい処理の名前を書き、<メソッド本体>には実際の命令を書く。

先ほどの例では、「sayHello」という名前の、以下のような本体を持ったメソッドを宣言していた。

System.out.println("Hello!");
System.out.println("This is sayHello.");

public staticがどんな意味であるか、publicやstatic以外にどのようなものがあるか、今は説明しない。staticと書かれたメソッドはクラスメソッドとも呼ばれる。したがって、今のところは、メソッドはすべてクラスメソッドである。

メソッドの宣言を書く位置は、今のところは次のように覚えておけばよい。

つまり、インデントをきれいにそろえた場合にはmainメソッドと同じレベルのインデントになる。

先ほどの例で確認できる。

public class SayHello {

  public static void main(String[] args) {
    sayHello();
  }

  public static void sayHello() {
    System.out.println("Hello!");
    System.out.println("This is sayHello.");
  }
}

以下のような2つの例は、それぞれコンパイルエラーになる。

public class SayHello2 {

  public static void main(String[] args) {
    sayHello();
  }

}
// クラスブロックの外側にあるのでエラー
public static void sayHello() {
  System.out.println("Hello!");
  System.out.println("This is sayHello.");
}
public class SayHello3 {

  public static void main(String[] args) {
    sayHello();
    
    // main メソッドの内側にあるのでエラー
    public static void sayHello() {
      System.out.println("Hello!");
      System.out.println("This is sayHello.");
    }
  }
}

このような決まりを守れば、メソッドはクラス内にいくつ宣言しても良い。ただし、同じ名前のメソッドは同じクラス内で2つ以上宣言しないこと。同じ名前のメソッドを同一クラス内に宣言する方法もあるが、ここでは説明しない。

public class SayHello4 {

  public static void main(String[] args) {
    sayHello();
  }

  public static void sayHello() {
    sayHello1();
    sayHello2();
  }

  public static void sayHello1() {
    System.out.println("Hello!");
  }

  public static void sayHello2() {
    System.out.println("This is sayHello.");
  }
}

上記の例では、mainメソッド以外に3つのメソッドを宣言し、sayHelloからsayHello1とsayHello2を順番に起動している。

メソッドの起動

メソッドを起動 (呼び出す) には、起動したい位置で次のような命令を書けばよい。

<メソッドの名前>();

ここで、<メソッドの名前>には必ず宣言を行ったメソッドの名前を入れること。

先ほどの例では、宣言した「sayHello」という名前のメソッドを

sayHello();

とやることによって起動していた。

public class SayHello {

  public static void main(String[] args) {
    sayHello();
  }

  public static void sayHello() {
    System.out.println("Hello!");
    System.out.println("This is sayHello.");
  }
}

引数のあるメソッド

これまでの例では、単純に「常に同じ処理を行うメソッド」を宣言/起動していた。しかし実際にメソッドが使用される場面は「ほとんど同じ処理を行う」ような場合である。「ほとんど同じ処理を行う」ような場合というのは、これまでに既に直面している。

System.out.print(100);
System.out.print(200);

System.out.println(...) という命令は、「コンソール画面へ文字列を表示する」というようなメソッドである。ただし、()内に書かれた文字列によって表示させる文字列を変えることができる。つまり、メソッドを起動する際に何らかの値を渡して、その値によって少しだけ違う処理をさせるような命令を作成している。

このようなメソッドを作成する場合、値を受け取るための変数を一緒に定義することによって実現できる。

public class LoopSayHello {

  public static void sayHello(int n) {
    for (int i = 0; i < n; i++) {
      System.out.println("Hello!");
    }
  }

  public static void main(String[] args) {
    sayHello(10);
  }
}

上記の例にあるsayHelloメソッドは、呼び出す際に指定された回数だけ Hello! と表示するようなメソッドである。

public static void sayHello(int n)

のように、「回数」を保存する受け皿として int 型の変数 n を宣言し、for文の中で使用している。

for (int i = 0; i < n; i++) {

また、起動する側も「10回」と指定してメソッド sayHello を起動している。

sayHello(10);

このような受け皿の変数を「メソッドの引数 (パラメータ)」と呼ぶ。詳しく言うと、

public static void sayHello(int n)

のように、受け皿の方の引数を「仮引数」と呼び、

sayHello(10);

のように、受け皿に渡す方を「実引数」と呼ぶ。

この例では、呼び出す回数を定数 10 で指定したが、定数ではなくて一般的な式を用いることができる。

import java.io.*;

public class FlexLoopSayHello {

  public static void sayHello(int n) {
    for (int i = 0; i < n; i++) {
      System.out.println("Hello!");
    }
  }

  public static void main(String[] args) throws IOException {
    BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
    int count = Integer.parseInt(reader.readLine());
    sayHello(count * 2);
  }
}

また、メソッドに受け渡す引数を2つ以上にすることもできる。

public class Adder {

  public static void add(double x, double y) {
    System.out.println(x + y);
  }

  public static void main(String[] args) {
    add(10.5, 12.3);
    add(-2.3, 2.4);
  }
}

上記の例のように、仮引数が2つ以上ある場合はそれぞれを「, (カンマ)」で区切り、実引数も同じ形で「,」区切りで渡してやればよい。

まとめると、メソッドの定義の形は、今のところは以下の形である。

public static void <メソッドの名前> (<仮引数>) {
  <メソッド本体>
}

ここで、<仮引数>がない場合には、その部分は何も書いてはならない。2個以上ある場合は

メソッドの起動時に注意すべきことは、仮引数と実引数の数をそろえてやる必要がある。例えば、先ほどの例の

public static void add(double x, double y)

という仮引数を2つ持つメソッドを

add(15);

というように実引数1つだけで起動することはできない。

メソッド起動の流れ

メソッドを起動すると、実引数の値が仮引数に代入されてメソッドの本体が実行される。メソッドを実行し終わったら、メソッドを起動した命令の次の命令を続けて実行することになる。

public class Adder {

  public static void add(double x, double y) {
    System.out.println(x + y);
  }

  public static void main(String[] args) {
    add(10.5, 12.3);
    add(-2.3, 2.4);
  }
}


mainメソッド

いままで public static void main(String[] args) { /* ... */ } とやっていた main メソッドも、普通のメソッドである。引数にある String[] についてはいずれ説明する。

mainメソッドはプログラムを実行する環境から直接呼び出され、mainメソッドの実行が終了するとプログラム全体が終了するようになっている。

変数のスコープ

変数にはスコープ (有効範囲) というものがあり、宣言した変数はある一定の範囲内でしか使用できない。

例えば、次のような例を考える。

for (int i = 0; i < 10; i++) {
  // ...
}
// ERROR: ここで変数 i は使用できない
System.out.println(i);

コメントにもあるように、上記の例では System.out.println(i) の行がコンパイルエラーになる。ここでは for の初期化式で宣言した int 型の変数 i が使用できないためである。これは、変数 i の有効範囲で使用しているためで、もしも i の値を利用したいなら以下のようにすれば良い。

// i を for の外で宣言することによって、 for の外側でも利用できるようにする
int i;
for (i = 0; i < 10; i++) {
  // ...
}
// これなら変数 i を使用できる
System.out.println(i);

上記の例ならば、System.out.println(i) はコンパイルエラーにならない。先述の例との差異は、変数 i を宣言している場所の違いである。前者の例は変数 i の有効範囲は for 文の中のみで、後者の例は for 文が終了した後も変数 i は有効である。

しかし、上のようなプログラムで

for (int i = 0; ...

といったようにするとコンパイルエラーになってしまう。これは、変数 i を2回以上宣言しているためである。

ローカル変数のスコープ

ローカル変数 (これまでの演習で使用してきたような変数のこと) のスコープは、宣言を行った直後から、その宣言のあるブロック ({ ... }のこと) の末尾までである。

public static void methodScope() {
  // ここではまだ input が有効範囲外 (宣言されていない)
   // ... (何らかの処理)
 int input = Integer.parseInt(reader.readLine()); // input の有効範囲を開始
    
  // 有効範囲内なので使用できる
  System.out.println(input);
  
} // input の有効範囲終了

上記の例では、inputという変数を宣言している。このinputという変数が属しているブロックはメソッドの宣言時に作成したブロックである。

public static void methodScope() {
  ...
}

つまり、上記の範囲内で input という変数を使用することができる。

public static void methodForScope() {
  
  // ここではまだ col が有効範囲外 (宣言されていない)
  
  for (int i = 1; i <= 9; i++) {
    int col = i * 5; // col の有効範囲を開始
    
    // 有効範囲内なので使用できる
    System.out.println(col);
    
  } // col の有効範囲終了
  
  // ここでは有効範囲外なので col を使用できない (ブロックを抜けた)
}

for 文の初期化式で宣言された変数 i のスコープは、その宣言の直後から for 文で繰り返されるブロックの末尾までである。
つまり、col の有効範囲が終了すると同時に i の有効範囲も終了している。

上の例では for 文を用いたが、if-else 文やメソッドでも同じである。

public static void methodIfScope() {
  
  // ここではまだ output が有効範囲外 (宣言されていない)
  // ... (何らかの処理)
  
  if (input < 0) {
    int output = -input; // output の有効範囲を開始
    
    // 有効範囲内なので使用できる。
    System.out.println(output);
    
  } // output の有効範囲終了
  
  else {
    // if 文で宣言した output の有効範囲は切れているので、再度宣言できる
    int output = input; // output の有効範囲を開始 (先ほどのoutputとは別物になる)
    
    // 有効範囲内なので使用できる。
    System.out.println(output);
    
  } // output の有効範囲終了
  
  // ここでは有効範囲外なので output を使用できない (ブロックを抜けた)
}

パラメータ変数のスコープ

メソッド宣言時に記述されたパラメータ変数の宣言は、そのメソッド内で有効である。つまり、以下のようなプログラムはエラーになる

public class ParameterScope {
  public static void hoge(int foo) { // foo の有効範囲を開始
    ...
  } // foo の有効範囲を終了
  
  public static void main(String[] args) {
    // ここでは foo を使用できない (foo は有効範囲外)
  }
}

パラメータの値渡し (call by value)

メソッドにパラメータとして変数を渡す場合、変数そのものではなく変数の中身を渡す。

public class CallByValue {
  public static void main(String[] args) {
    int a = 10;
    int b = 20;
    swap(a, b);
    System.out.println("a = " + a);
    System.out.println("b = " + b);
  }

  // 二つの変数の中身を交換する (値渡しをするので失敗)
  public static void swap(int x, int y) {
    int temp = x;
    x = y;
    y = temp;
  }
}

上記の例を実行すると、

a = 10
b = 20

と表示され、swapメソッドを起動した意味がない。これは、変数の中身(10と20)をコピーしてswapメソッドを呼んでいるため、コピーされた値を交換したところで main メソッドで宣言した変数 a, b の値が変更されることはない。