今までメソッドmainに呪文のように付いてきた「static」、今宵その謎が解き明かされます。
今まで切り捨てやら切り上げに付き物だった「Math」。その正体も明かされるときがきました。
What is 「this」?
「static」に入る前に問題をひとつ。次で何が出力されるでしょうか?
[A.java]
public class A { private int x; public A(int x ) { this.x = x; } public int methodA(int p) { return this.x + p; } }
[Main.java]
public class Main { public static void main(String[] args) { A p = new A(1); A q = new A(2); System.out.println(p.methodA(10)); System.out.println(q.methodA(10)); } }
ばかにするな!と言われそう。そう
11
12
です。二つのメソッド呼び出し
p.methodA(10) と q.methodA(10) は
実引数の値はどちらも10なのに、どうして戻り値がちがうのでしょう?呼び出しているオブジェクトがpとqでちがうためですね。
実引数の値10は、メソッドmethodAの仮引数pで受け取ります。では、どのオブジェクトで呼び出しているか、pかqか、という情報はmethodAの何で受け取っているのでしょうか?もうおわかりですね。「this」です。methodA内の「this.x」の値が二つの呼び出しで異なるため、戻り値がちがってきたのです。ついでにmethodA内の「this」は省略できることを確認しておきます。(今までめんどうでもつけてきたのは、もしかして正解だったかなとニヤニヤしています。)
このようにpやqといった、あるオブジェクトについて呼び出されるメソッドをインスタンスメソッドといいます。今まで出てきたメソッドはすべてインスタンスメソッドです。(メソッドmainだけは例外です。)インスタンスメソッドの中では「this」でそのオブジェクトを参照できます。ここまでは用語以外、特に目新しいことは出てきていませんね。
今回はインスタンスメソッドでないメソッドについて解説していこうと思います。そのためにちょっと確認の回り道をしました。
Mathの正体
インスタンスメソッドでないメソッドをクラスメソッドといいます。クラスメソッドの作り方は簡単。頭に「static」をつければOKです。おやおやメソッドmainでも付いてましたね。mainは特別なメソッドなのでまた後で別に解説します。
ではクラスメソッドはどんな場合に使うのでしょうか。それは、インスタンスメソッドと違い、オブジェクトの状態に関係しない場合です。つまり必要な情報は引数ですべて受け取ります。次のmethodBはクラスメソッドになります。
[A.java]
public class A { private int x; public A(int x ) { this.x = x; } public int methodA(int p) { return this.x + p; } public static int methodB(int p) { return p * p; } }
[Main.java]
public class Main { public static void main(String[] args) { A p = new A(1); A q = new A(2); System.out.println(p.methodA(10)); System.out.println(q.methodA(10)); System.out.println(p.methodB(10)); //警告 System.out.println(q.methodB(10)); //警告 System.out.println(A.methodB(10)); //よい System.out.println(A.methodB(10)); //よい } }
メソッドmethodBは単に仮引数に受け取った値を2乗して戻しているだけです。結果は次のようになります。
11
12
100
100
100
100
オブジェクトpで呼ぼうが、qで呼ぼうが、methodBの結果は同じです。本当はオブジェクトがなくても、クラス名Aで呼び出せます。そしてこちらがお薦めです。実際オブジェクトで呼び出すと警告が出てしまいます。
引数だけにしか戻り値が依存しないのであれば、methodBは特にクラスAになくてもいいじゃないか、と思われるでしょう。確かにそのとおり。ですが、関係するクラスに記述しておきます。
例えば標準ライブラリのクラスStringのメソッドvalueOfは、引数に指定された、booleanやdouble、intのデータを文字列に変換してくれます。staticのついたクラスメソッドですが、文字列への変換ということでクラスStringに入っています。
絶対値、三角関数、指数関数などの、数学関係のメソッドは、クラスMathにまとめてクラスメソッドとして定義されています。クラスMathはオブジェクトを作れないクラスで、ユーティリティクラスとしての役割を持ちます。
次に使用例を載せておきます。StringもMathもjava.langパッケージにあることはいいですね。(だからimport文は必要ないわけです。)
public class Main { public static void main(String[] args) { boolean b = false; int i = 123; double d = 0.000000123; String s = String.valueOf(b) + " " + String.valueOf(i) + " " + String.valueOf(d); System.out.println(s); System.out.println(Math.abs(-1.5) + " " + Math.ceil(1.23) + " " + Math.sqrt(2)); System.out.println(Math.PI + " " + Math.E); //次で解説 } }
結果は次のようになります。
false 123 1.23E-7
1.5 2.0 1.4142135623730951
3.141592653589793 2.718281828459045
1番 nyanta
インスタンスメソッドとクラスメソッドがあるのならば、インスタンス変数に対してクラス変数があるのではないか?ピンポーン!
インスタンス変数は、オブジェクトごとに作られるのに対し、クラス変数はクラスについてひとつだけ作られます。お察しの通り宣言で「static」を頭につけます。
次のクラスCatは、1、2、3...と通し番号付きの猫のオブジェクトを作る例です。オブジェクトを生成したときにどんな番号を振ればいいのかは、クラス変数nextNoに入れておきます。オブジェクト生成時、インスタンス変数noにnextNoの値を代入します。このとき次のオブジェクトのために、nextNoを1増やしておかねばなりません。
[Cat.java]
public class Cat { private static int nextNo = 1; private int no; private String name; public Cat(String name) { this.name = name; this.no = nextNo++; } public String toString() { return "No:" + this.no + " " + this.name; } public static int getNextNo() { return nextNo; } public void setName(String name) { this.name = name; } }
[Main.java]
public class Main { public static void main(String[] args) { Cat x, y, z; x = new Cat("nyanta"); //① y = new Cat("nyanko"); //② z = new Cat("myao"); //③ System.out.println(x); System.out.println(y); System.out.println(z); System.out.println(Cat.getNextNo()); } }
出力結果は次のようになります。
No:1 nyanta
No:2 nyanko
No:3 myao
4
クラスはオブジェクト製造機でしたね。このイメージで絵を載せておきます。プログラム中の①~③が絵の①~③に対応します。クラスCatの上についているのがクラス変数nextNoです。オブジェクト生成のたびに1ずつ増えていますね。
なおメソッドgetNextNoはクラス変数nextNoにアクセスするだけなので(thisは使われていませんよね)staticを付けクラスメソッドにしてあります。すると呼び出しも
Cat.getNextNo()
とクラス名でできます。こんなクラスメソッドの使い方もあります。
初志貫徹
クラス変数の重要な使い道に定数があります。クラス変数に変更不可の「final」の指定をします。
public static int N = 123;
Nは変更付加なので、
N = 200;
などとするとエラーになります。そこでNは定数123につけた名前である、として扱うことができるのです。
クラス型の定数になると少し話はややこしくなります。
public static final Cat NEKO = new Cat("neko");
変数NEKOの値は変更不可なので、次はエラーになります。
NEKO = new Cat("nyan");
ところが、NEKOの中身である参照値は変更されていないので、次はエラーになりません。
NEKO.setName("nyan");
次の図で確かめておきましょう。
クラスMathには円周率のPI、自然対数の底eの値Eが定義されています。クラス名を頭につけてMath.PIやMath.Eとして利用できます。前の例の最後に出力しておきました。Math.PIの場合、クラスMathに次のように宣言されています。
public static finel double PI = 3.14159265358979323846;
入口がたくさん
メソッドmainは、そこから実行が始まる特別なクラスでした。最初はひとつもオブジェクトが作られていないので、mainはクラスメソッドでなければならないのです。
クラスのテストにメソッドmainが使えます。次のクラスAのメソッドmethodAが思ったとおり動くかテストするとき、今まで別クラスにmainを入れていました。
[A.java]
public class A { private int x; public A(int x) { this.x = x; } public void methodA() { System.out.println(x); } }
[Main.java]
public class Main { public static void main(String[] args) { A objA = new A(5); objA.methodA(); } }
これをコマンドラインからコンパイル、実行するには
javac Main.java
java Main
とします。ところがクラスAにmainを突っ込んでしまうこんな方法もあります。
[A.java]
public class A { private int x; public A(int x) { this.x = x; } public void methodA() { System.out.println(x); } public static void main(String[] args) { A objA = new A(5); objA.methodA(); } }
これをコマンドラインでコンパイル、実行するには
javac A.java
java A
とします。テスト用に別クラスを作らなくて済むのですっきりしますね。
普通プログラムはいくつものクラスで構成されています。一つクラスができると、まずはそれが単体でちゃんと動くか調べなければなりません。そんなときは、そのクラスの中にテスト用のメソッドmainを入れておくのです。
ではクラスAのテストが終わったら、プログラムの実行に関係ない、このmainは取ってしまわなければならないのでしょうか?いいえ、入れておいてください。他のクラスとの関係で、クラスAの仕様が変更されることもありますから。そのときはまたこのmainでうまく動くかテストできますから。
他のクラスも同様に単体テスト用のmainを入れておきます。実際にプログラム全体を動かす場合のmainはまた別に作るでしょうから、問題ありません。テストのときはmainクラスとしてテストしたいクラスを指定すれば、その中のmainが動きます。
クラスAのテスト java A
全体の起動 java Main
argsの正体
メソッドmainの1行目は何はともあれ次のように書き出していました。
public static void main(String[] args) {
publicでstaticで、戻り値は返せないのでvoidの指定になるのはいいですね。ではカッコ内の
String[ ] args
はなんでしょう?実はargsは仮引数で、実行時にここにデータを受け取ることができるのです。仮引数の名前はargsでなくてもいいのですが、これ以外の名前を使っているのを見たことはありません。argsを使っておきましょう。
次は、実行時の引数の値を出力するものです。
[Main.java]
public class Main { public static void main(String[] args) { for(int i = 0; i < args.length; i++) { System.out.println(args[i]); } } }
これをコマンドラインから次のようにコンパイル、実行した場合、下の図のようにString型の配列argsに値が入ります。(Eclipseの場合、プログラムの引数に下線の部分を指定してください。)
javac Main.java
java Main 33 true ABC
これをプログラムでは順に書き出しています。次のように出力されます。
33
true
ABC
気をつけてほしいのは、すべて文字として見なされるということです。33は文字の3と3であって、数値の33ではないということです。
もうひとつ、C言語ではプログラムの名前も引数に入ったりしてまぎらわしかったのですが、Javaではプログラム名の後ろの部分のみ引数に入ります。気をつけましょう。