ここまで、条件式と繰り返しについて学んできました。本章ではstaticなメソッドについて学習していきます。なお、メソッドはstaticなメソッドまたはインスタンスメソッドのどちらかになります。インスタンスメソッドについては後の章で解説します。まずはこの章で、staticなメソッドについて解説していきます。
なお、中学数学や高校数学では関数(一次関数や二次関数など)を習ったかと思います。staticなメソッドとは、それをより一般化したものとも言えます。
さて、消費税の税込み価格を計算するプログラムを製作することにします。仕様を以下に定めます。
この仕様を満たすソースコードを以下のように製作しました。
G101/MySystem.java(ライブラリをそのまま利用します)
G101/G101.java
/**
* 消費税を含んだ価格を計算します。
*/
public class G101 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
int price;
price = MySystem.in.getInt("税抜き価格を入力してください");
if(price < 0) {
System.out.println("価格が異常です");
} else {
int taxIncludedPrice = G101.getTaxIncludedPrice(price);
System.out.println(price + "円は税込みで " + taxIncludedPrice + "円です。");
System.out.println("ちなみに、");
System.out.println("2000円の場合は" + G101.getTaxIncludedPrice(2000) + "円になります。");
System.out.println("5000円の場合は" + G101.getTaxIncludedPrice(5000) + "円になります。");
}
}
/**
* 税込み価格を返します。
* @param p 税抜き価格
*/
private static int getTaxIncludedPrice(int p) {
// 消費税率は 5%として計算する
double temp = (double)p * 1.05;
int tip = (int)temp;
return tip;
}
}
実行結果の例は以下の通り。
税抜き価格を入力してください? 980 980円は税込みで 1029円です。 ちなみに、 2000円の場合は2100円になります。 5000円の場合は5250円になります。
上記のソースコードで注目すべき点には、以下のものがあります。
staticが使用されている。privateについては後の章にて説明します。このソースコードにおいてはpublicと記述しても実行結果は変わりません。intと記述されている。getTaxIncludedPriceメソッドの戻り値の型は int型となる。priceの値が、22行目では 2000が、23行目では 5000という値が、getTaxIncludedPriceメソッドに渡される。pに代入される。tipに値が格納される。returnが使用されている。returnの後には税込み価格が格納されている変数tipが指定されている。1.05の 1ヶ所を変更するだけでプログラムの修正を終えることができる。計算を 3回実施しているにも関わらず、変更は 1ヶ所で済む。なお、staticなメソッドについては、以下のことも合わせて覚えてください。
G101.getTaxIncludedPrice(~)と記述しました。実はG101.の部分は省略可能で、getTaxIncludedPrice(~)と記述しても大丈夫です。そして、世の中一般には省略することが普通なのですが、本ウェブサイトでは省略を一切しないで記述していきます。というのも、省略すると、staticなメソッドと(後の章で登場する)インスタンスメソッドとの違いが分かりづらくなるため、という理由があります。仕様を以下に定めます。
この仕様を満たすソースコードは以下の通り。
G102/G102.java
/**
* 1~10の階乗を表示します。
*/
public class G102 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
for(int i = 1; i <= 10; i ++) {
long value = G102.factorial(i);
System.out.println(i + "の階乗は " + value);
}
}
/**
* 指定した数の階乗を求めます。なお、0以下の値を指定した場合、1を返します、注意。
* @param number 数
* @return 階乗の値
*/
private static long factorial(int number) {
long value = 1;
for(int i = number; i >=1 ; i --) {
value *= i;
}
return value;
}
}
実行結果は以下の通り。
1の階乗は 1 2の階乗は 2 3の階乗は 6 4の階乗は 24 5の階乗は 120 6の階乗は 720 7の階乗は 5040 8の階乗は 40320 9の階乗は 362880 10の階乗は 3628800
mainメソッド内の変数 iと factorialメソッド内の変数 iは、たまたま変数名が一緒なだけで別物の変数として扱われていることも分かるかと思います。
仕様を以下に定めます。
この仕様を満たすソースコードは以下の通り。
G103/MySystem.java(ライブラリをそのまま利用します)
G103/G103.java
/**
* mのn乗を計算します。
*/
public class G103 {
public static void main(String[] args) {
int a;
int b;
int value;
a = MySystem.in.getInt("1つ目の数を入力してください");
b = MySystem.in.getInt("2つ目の数を入力してください(0以上)");
if(b < 0) {
System.out.println("2つ目の数がマイナスです。");
} else if(a == 0 && b == 0) {
System.out.println("0の 0乗は計算できません。");
} else {
value = G103.power(a, b);
System.out.println(a + "の " + b + "乗は " + value + "です。");
System.out.println("ちなみに、");
System.out.println("3の 5乗は" + G103.power(3, 5) + "。");
System.out.println("2の 10乗は" + G103.power(2, 10) + "。");
}
}
/**
* mの n乗を返します。
* なお、nがマイナスの場合、計算結果が狂います、注意。
* また、0の 0乗についても、計算してしまいます、注意。
* @param m 値
* @param n かける回数
* @return 計算結果
*/
private static int power(int m, int n) {
int value = 1;
for(int i = 0; i < n; i ++) {
value *= m;
}
return value;
}
}
実行結果の例は以下の通り。
1つ目の数を入力してください? 5 2つ目の数を入力してください(0以上)? 3 5の 3乗は 125です。 ちなみに、 3の 5乗は243。 2の 10乗は1024。
1つ目の数を入力してください? 3 2つ目の数を入力してください(0以上)? 0 3の 0乗は 1です。 ちなみに、 3の 5乗は243。 2の 10乗は1024。
1つ目の数を入力してください? 4 2つ目の数を入力してください(0以上)? -2 2つ目の数がマイナスです。
1つ目の数を入力してください? 0 2つ目の数を入力してください(0以上)? 0 0の 0乗は計算できません。
先の 3つのプログラムでは、戻り値のあるメソッドを見てきました。ここでは戻り値のないメソッドについて解説していきます。以下のような形を使用します。
private static void メソッド名(引数, 引数, …) {
処理;
}
見ての通り、予約語voidを使用します。それが戻り値のないメソッドの特徴ということになります。なお、staticについては後の章で解説します、今は常に staticを使用します。また privateについても後の章で解説します、今のうちは privateを publicと書き換えても実行結果は変わりありません。
仕様を以下に定めます。
この仕様を満たすソースコードは以下の通り。
G104/MySystem.java(ライブラリをそのまま利用します)
G104/G104.java
/**
* 指定回数だけ文字列を表示します。
*/
public class G104 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
int count;
count = MySystem.in.getInt("回数を入力してください");
G104.cryWan(count);
System.out.println();
G104.cryWan(2);
G104.cryWan(1);
G104.cryWan(4);
}
/**
* 指定回数だけ文字列「ワン」を表示します。
* @param count 回数
*/
private static void cryWan(int count) {
for(int i = 0; i < count; i ++) {
System.out.print("ワン");
}
System.out.println();
}
}
実行結果の例は以下の通り。
回数を入力してください? 10 ワンワンワンワンワンワンワンワンワンワン ワンワン ワン ワンワンワンワン
void型のメソッド cryWanは、戻り値を返す必要がありません。したがって、予約語returnは必ずしも記述する必要がありません。言い換えれば、今回の場合、31行目の 32行目の間に、return;が省略されていると考えることもできます。
これまで使用してきた mainメソッドも、voidの文字が使用されています。したがって、mainメソッドも戻り値を返す必要がなく、今まで予約語returnを使用する必要に迫られなかった、ということでもあります。
あるメソッドがその中で別のメソッドを呼び、さらにそのメソッドが別のメソッドを呼ぶ…という構造を組み上げることで、より大きな規模のプログラムを、簡単に製作できるようになっていきます。
また、1つのメソッドの中身があまりにも巨大だと、それを人間が理解するのは大変です。ある一定の規模になったら、別のメソッドに分離独立させていくと、比較的読みやすいソースコードになる可能性が高まります。
メソッドを入れ子にした例をひとつサンプルとして挙げてみます。
以前、if文の解説の際に、うるう年かどうかを判定するプログラムを作りました。今回はそのプログラムを改めて製作してみることにします。仕様を以下に定めます。
この仕様を満たすソースコードは以下の通り。
G105/MySystem.java(ライブラリをそのまま利用します)
G105/G105.java
/**
* うるう年を判定します。
*/
public class G105 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
int year;
System.out.println("[うるう年判定プログラム]");
year = MySystem.in.getInt("西暦を入力してください");
if(year < 0) {
System.out.println("マイナスの値が入力されました。");
} else {
if(G105.isLeapYear(year)) {
System.out.println("入力された " + year + "年はうるう年です。");
} else {
System.out.println("入力された " + year + "年はうるう年ではありません。");
}
System.out.println("ちなみに、");
G105.printLeapYear(1900);
G105.printLeapYear(2000);
G105.printLeapYear(2012);
G105.printLeapYear(2013);
}
}
/**
* 指定した年がうるう年かどうかを表示します。
* @param year 年
*/
private static void printLeapYear(int year) {
if(year < 0) {
System.out.println("西暦がマイナスです。");
} else if(G105.isLeapYear(year)) {
System.out.println(year + "年はうるう年です。");
} else {
System.out.println(year + "年はうるう年ではありません。");
}
}
/**
* 指定した年がうるう年かどうかを判定します。
* 年がマイナスでも計算してしまいます、注意。
* @param year 年
* @return うるう年の場合、true
*/
private static boolean isLeapYear(int year) {
if(year % 400 == 0) {
return true;
} else if(year % 100 == 0) {
return false;
} else if(year % 4 == 0) {
return true;
} else {
return false;
}
}
}
実行結果の例は以下の通り。
[うるう年判定プログラム] 西暦を入力してください? 1996 入力された 1996年はうるう年です。 ちなみに、 1900年はうるう年ではありません。 2000年はうるう年です。 2012年はうるう年です。 2013年はうるう年ではありません。
mainメソッドが printLeapYearメソッドを呼び出し、printLeapYearメソッドが isLeapYearメソッドを呼び出していることが分かるかと思います。
あるメソッドが別のメソッドを呼び、そのメソッドが別のメソッドを呼び…それが無限ループのようになってしまったら、実行結果はどうなるのかを実験してみます。
サンプルプログラムは以下の通り。
G106/G106.java
/**
* メソッドの無限呼び出しをします。
*/
public class G106 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
// 無限ループするメソッドを呼び出す
G106.loop();
}
/**
* メソッドを無限に呼び出します。
*/
private static void loop() {
G106.loop();
}
}
実行結果は以下の通り。
Exception in thread "main" java.lang.StackOverflowError at G106.loop(G106.java:19) at G106.loop(G106.java:19) at G106.loop(G106.java:19) (省略) at G106.loop(G106.java:19)
プログラムは延々と終了しないのではなく、強制終了となります。この StackOverflowErrorという強制終了については、ずっと後の「エラーと例外」のところで解説します。無限のメソッド呼び出しは強制終了することについては覚えておいてください。
なお、「実行時に落ちる」例は、ずっと以前、「ハローワールド」を作成したところでさらっと紹介して以来、2例目となります。あのときは ArithmerticExceptionという Exception系の強制終了でした。今回は、StackOverflowErrorという Error系の強制終了となります。この Exception系と Error系の違いについては、後の章(例外という章)にて詳しく解説していきます。
Javaは、C言語の文法の影響を大きく受けていると思います。ただ、予約語 staticについては、C言語での使われ方と Javaでの使われ方はまったく異なります。C言語を学習済みの方は、予約語 staticに関しては、先入観を排除して把握したほうが理解は早まると思います。
Javaには、たくさんのメソッドが用意されています。その中には staticなメソッドもあれば、staticでないメソッド(=インスタンスメソッド)もあります。一部の staticなメソッドについてはここまで学んできたことを踏まえて利用することができます(なお、大半の staticなメソッドについては、予約語importを使った記述を必要としますので、詳細は後の章に記述します)。
これまで時折、Math.randomメソッドを使用してきました。以下の感じで。
double a = Math.random();
これは Mathクラスに存在する staticなメソッド randomを利用していたのです。
Java SE 7(Java 1.7)の Mathクラスの説明書(API)を見てみることにします。次のリンクをクリックしてください→Mathクラス。この中に randomメソッドの記述を発見できたでしょうか。randomメソッドの説明文の中身については専門的な話なので分からなくて構いません。ただ、randomメソッドが staticで、また戻り値の型が double型であることは分かると思います。
実は、Mathクラスに登録されているすべてのメソッドは staticです。したがって、randomメソッドと同じような感覚で、Mathクラスの説明書(API)に書かれた他のメソッドを利用することができます。mの n乗を求めたり、平方根を求めたり、絶対値を求めたりできるメソッド、三角関数や対数を扱うためのメソッドが用意されています。説明書(API)の記述を頼りにしながら試しに利用してみると、勉強になるかと思います。
先ほどの Mathクラスを見ていると、同じメソッド名にも関わらず、引数の型が異なるものが別物として扱われていることが分かります。例えば、absメソッドには以下の 4つがあることが分かります。
このように引数の型が異なる場合の他、引数の数が異なったり、(複数の引数がある場合には)引数の型の並び順が違う場合、Javaでは、同じメソッド名でも別物として扱われます。このことをオーバーロードと呼びます。なお、しばらく後の章で、オーバーライドという言葉が登場しますので、混同しないように注意してください。
引数の数が異なるサンプルプログラムを以下に挙げます。
G107/G107.java
/**
* オーバーロードを確認します。
*/
public class G107 {
/**
* メインメソッド。
* @param args 引数
*/
public static void main(String[] args) {
G107.cryWan(2);
G107.cryWan();
G107.cryWan(4);
}
/**
* 指定回数だけ文字列「ワン」を表示します。
* @param count 回数
*/
private static void cryWan(int count) {
for(int i = 0; i < count; i ++) {
System.out.print("ワン");
}
System.out.println();
}
/**
* 文字列「ワン」を表示します。
*/
private static void cryWan() {
G107.cryWan(1);
}
}
実行結果は以下の通り。
ワンワン ワン ワンワンワンワン
このサンプルプログラムの場合には、引数 1個の cryWanメソッドと引数の無い cryWanメソッドが存在します。今回の場合、引数の無い cryWanメソッドは、事実上、引数の省略を手助けするような形になっています。