静的メソッド

前ページのプログラムで,p1とp2の距離を求める部分
    dx=(p1.x-p2.x);dy=(p1.y-p2.y);
    r=Math.sqrt(dx*dx+dy*dy);
のp1 -> p2, p2-> p3と置き換えると,p2とp3の距離を求める部分である,
    dx=(p2.x-p3.x);dy=(p2.y-p3.y);
    r=Math.sqrt(dx*dx+dy*dy);
になる.このように「変数の置き換えをすれば同じになる部分」を抜き出して, 一カ所で書けるようにしたものが,静的メソッド呼び出しである.Basicなど のサブルーチン,C言語での関数などにあたる.
外部からも呼び出し可能な静的メソッドは,クラスの内部で,
public static 返り値の型 メソッド名(引数宣言){
  メソッド本体
}
のように書いて宣言する.「変数の置き換え」にあたる部分を宣言するのが, 引数(ひきすう)宣言部である.

置き換えたい部分,

    dx=(p1.x-p2.x);dy=(p1.y-p2.y);
    r=Math.sqrt(dx*dx+dy*dy);
で,変数 p1, p2 は値を参照されるだけなので,引数にする.同じクラス内の 静的メソッドを呼び出す際は,「メソッド名(引数1,引数2, .. , 引数n)」の ように書く.

dx, dyはここで値を代入されるが,rを求めた後は参照されないので,この部 分だけで有効なローカル変数(local variable)にする.

変数rだけはこの部分を実行後にも値を使いたいので,return 文によって,値 を返す.返したい値の型は,doubleなのでメソッドの返り値の型も doubleに なる.
上で述べたように,メソッドでは1つの値しか返すことができない.複数の値 を返したい時は,複数の値をまとめたようなクラスを作って,それを返すメソッ ドにする方法が一般的である.


先ほどのプログラムを書き直したものをあげる.
class Point2D {
  public double x,y;
}

public class ObjectTest2 {
    // Point2Dクラスの point1とpoint2の間の距離(2次元における)を返す
    // 静的メソッド distance2Dの定義
  public static double distance2D(Point2D point1,Point2D point2){
      // point1, point2のx座標の差を dxに、y座標の差を dyに入れる
    double dx=(point1.x-point2.x),dy=(point1.y-point2.y);
      // dxとdyの2乗和の平方根を返す。
    return Math.sqrt(dx*dx+dy*dy);
  }
  public static void main(String[] args){
    Point2D p1,p2,p3;
      // Point2Dクラスのインスタンスを作成し、p1に入れる。
    p1=new Point2D();
    p1.x=0.0; 
    p1.y=0.0;
    p2=new Point2D();
    p2.x=1.0; 
    p2.y=1.0;
    p3=new Point2D();
    p3.x=0.0; 
    p3.y=1.0;
      // p1とp2の間の距離を表示する。
    System.out.println("distance between p1 and p2 "+distance2D(p1,p2));
    System.out.println("distance between p2 and p3 "+distance2D(p2,p3));
    System.out.println("distance between p3 and p1 "+distance2D(p3,p1));
  }
}

上の例では,同じクラスの静的メソッドの呼び出しを,
    System.out.println("distance between p1 and p2 "+distance2D(p1,p2));
のようにおこなったが,静的メソッドは省略しないで書くと,
    System.out.println("distance between p1 and p2 "+ObjectTest2.distance2D(p1,p2));
のように,「型名.メソッド名」という形をしている.よく使われるあるパター ンをどのクラスの静的メソッドにするかというのは,プログラムの設計の問題 であり,難しい選択となることもあるが,上の例ではdistance2D という概念は Point2Dというクラスに付随するものなので,Point2Dクラスの静的メソッドに した方がよいだろう.そうすると,
class Point2D {
  public double x,y;
    // distance2Dという概念はPoint2Dクラスに付随すると考えられるので、
    // Point2Dクラスの静的メソッドとして定義 
  public static double distance2D(Point2D point1,Point2D point2){
    double dx=(point1.x-point2.x),dy=(point1.y-point2.y);
    return Math.sqrt(dx*dx+dy*dy);
  }
}

public class ObjectTest3 {
  public static void main(String[] args){
    Point2D p1,p2,p3;
    p1=new Point2D();
    p1.x=0.0; 
    p1.y=0.0;
    p2=new Point2D();
    p2.x=1.0; 
    p2.y=1.0;
    p3=new Point2D();
    p3.x=0.0; 
    p3.y=1.0;
      // 静的メソッドの呼び出しは、「クラス名.メソッド名」でおこなう。
    System.out.println("distance between p1 and p2 "+Point2D.distance2D(p1,p2));
    System.out.println("distance between p2 and p3 "+Point2D.distance2D(p2,p3));
    System.out.println("distance between p3 and p1 "+Point2D.distance2D(p3,p1));
  }
}
となる.

基本クラスの静的メソッドの例

これまで出てきた,システムの基本クラス Stringクラス の静的メソッドの例を見る. valueOf(int)というメソッドは
class StTest{
  public static void main(String[] args){
    int i=100;
    String s=String.valueOf(i);
    System.out.println(s+s);
  }
}
のように,intからStringに変換するのに使うメソッドだが,Stringのインス タンスとは直接には結び付いていないメソッドなので,静的メソッドとなる. 実行例は以下のようになる.
dell.tanaka.ecc.u-tokyo.ac.jp% java StTest
100100

再帰

あるメソッドの中で, 自分のメソッドを呼び出すことを「再帰(recursion)」 と呼び, プログラムの実用上のテクニックとしても, プログラムの理論的解析 においてても重要な概念である. 以下のプログラムは,Vを書くプログラムを, 再帰を使って書いた例である.メソッド spacesの定義の中で spaces 自身を 呼び出している部分が再帰にあたる.
class Test11_3{
   //
  static void spaces(int l){
    if(l==0) return;
    System.out.print(" ");
    spaces(l-1);
  }
  static void star(){
    System.out.print("*");
  }
  public static void main(String[] argv){
    int y;
    for(y=0;y< 6;y=y+1){
      spaces(y);
      star();
      spaces(10-y*2);
      star();
      System.out.println();
    }
  }
}
なお,メソッドspacesを再帰を使わずに書くと,
  static void spaces(int l){
    int i;
    for(i=0;i< l;i++)
      System.out.print(" ");
  }
のように簡単に書ける.再帰を使ったものと対応が取れるように書き直すと,
  static void spaces(int l){
    for(;l >0;l--)
      System.out.print(" ");
  }
となる.上のようにfor文の初期化部分がない場合は何も入れなければ良い.

この例ではわざわざ再帰を使って書く必要はないが,再帰を使った方が自然に 分かりやすくかける場合も多い.


課題1

次のプログラムは再帰を使って最大公約数を計算するプログラムである. なお このアルゴリズム(計算の方法)はユークリッドの互除法として良く知られてい る(GCDは Greatest Common Divisorの略).
class GCDTest {
  // ユークリッドの互除法により最大公約数を計算するメソッド gcd の定義
  // int型の引数 n, m をとり, int型の値を返す
  static int gcd(int n,int m){
    // デバッグ(プログラムのミスを直す)のための出力
    System.out.println("gcd("+n+","+m+")が呼ばれました");
    // 剰余を求めて rに入れる
    int r=n%m;
    // nがmの倍数になっているなら最大公約数は m自身になる.
    if(r==0) return m;
    // n,mの最大公約数は m,rの最大公約数と等しい
    else return gcd(m,r);
  }
  public static void main(String[] args){
    // コマンドラインから 
    // java GCDTest arg0 arg1 
    // のようにして起動した時, args[0] -> arg0, args[1] -> arg1 となる.
    // 文字列から int 型の整数値に変換するのは Integer.parseInt を使う
    int i0=Integer.parseInt(args[0]);
    int i1=Integer.parseInt(args[1]);
    System.out.println(i0+"と"+i1+"の最大公約数は"+gcd(i0,i1)+"です");
  }
}
実行例は以下のようになる.
dell.tanaka.ecc.u-tokyo.ac.jp% java GCDTest 96 1024
gcd(1024,96)が呼ばれました
gcd(96,64)が呼ばれました
gcd(64,32)が呼ばれました
96と1024の最大公約数は32です
  1. このプログラムのメソッドgcdを繰り返し文(for文や while文など)を使って(再帰を使わずに)書き直したプ ログラムを作りなさい.なお,
        System.out.println("gcd("+n+","+m+")が呼ばれました");
    
    にあたる表示は残しても残さなくても良い.
  2. 2数の最小公倍数を求めるメソッド lcm(Least Common Multipleの略)を 作成して,最大公約数,最小公倍数の両方を表示するプログラムを作成しなさ い.
    実行例:
    dell.tanaka.ecc.u-tokyo.ac.jp% java GCDTest 96 1024
    96と1024の最大公約数は32です
    96と1024の最小公倍数は3072です
    
    ヒント
    最小公倍数は(2数の積/2数の最大公約数)

どうして,
java クラス名
とやると、そのクラスの public static void main(String[]) という型のメ ソッドから実行を始めるというようにしたのか、疑問を持つ人がいるのは当 然だろう。実際、C++ではクラスに属さない一般の関数 main で始まっている。

Java言語では、プログラムの実行部分は全部メソッドでおこなわれ、すべて のメソッドをどこかのクラスに属するようにしたので、プログラム実行のスタ ート部分を特定のメソッド名に割り当てたということである.


次へ進む
ktanaka __at__ tanaka.ecc.u-tokyo.ac.jp