第2講 Java言語の基礎3のサブセクション
メソッド(関数)
基本的な呼び出し方.
C言語で言う関数をJava言語ではメソッド(method)と呼びます. メソッドは必ず何らかの実体に含まれています. そのため,メソッドを呼び出すときは,どの実体に対して呼び出すのかを指定しなければいけません. 例えば,クラス定義の基本形 に示した次のコードを見てみます.
public static void main(String[] args){
GivenClassName application = new GivenClassName();
application.run();
}このコードの3行目は,application.run() とメソッドを呼び出しています.
これは,application という実体に対して,run というメソッドを呼び出していることになります.
このように,必ず,何らかの実体を経由しなければメソッドは呼び出せません.
例題1 定義済みメソッドの呼び出し
コマンドライン引数で与えられた文字列を全て大文字に変換するプログラム,ToUpper を書いてみましょう.
以下のように出力されます.
$ java ToUpper tamada
TAMADA
$ java ToUpper tamada Java KSU_cse_AP
tamada -> TAMADA
Java -> JAVA
KSU_cse_AP -> KSU_CSE_APString 型の値を大文字に変更するには,toUpperCase メソッドを String 型変数に対して呼び出します.
すると,toUpperCaseの返り値として,全てが大文字に変更された文字列が返ってきます.
なお,String型の実体は不変(immutable)です.内容の変更はできませんので,元の変数の内容は変わりません.
参考コード
以下のコード片と ArgsPrinterが参考になるでしょう.
void run() {
String original;
original = "tamada";
String upper;
upper = original.toUpperCase();
// => original に代入されている文字が全て大文字になって,upperに代入される.
}このプログラムは,String型変数であるoriginal に対して,toUpper を呼び出し,
その返り値をupper というString型変数に代入しています.
Javaのあらゆるメソッドはこのように,何らかのものに対して呼び出します.
動作イメージを次の画像で確認してください.クリックするたびに画像が更新されます.
メソッドの定義方法
メソッドを定義する方法は,基本的にはC言語と同じです.異なる点は,Java言語の場合, メソッド宣言は必ずクラス宣言の内側でなければならない,という点です.
メソッドの典型的な宣言は次の通りです.
// クラス宣言
public class Xxxxx{
// ReturnType, ArgumentType は適切な型に読み替えてください.
ReturnType methodName(ArgumentType argumentName, ...){
}
}methodName はメソッド名を表しています.この部分はある程度好きな名前を付けられます.
処理の内容が後から見てわかるように付けましょう.ローマ字でも構いません.
また,ReturnTypeは返り値の型を表しており,適切な型に読み替えてください.
例えば,そのメソッドが返り値を持つ場合は,String や Integer など適切な型を宣言しましょう.
そのメソッドが何も値を返さない場合は,voidとしてください.省略はできません.
ArgumentType も適切な型に読み替えてください.
C言語の場合,引数に何も受け取らない場合,voidと書けましたが,Java言語では書けません.
引数に何も受け取らない場合は,methodName() のように,括弧内には何も書かないようにしましょう.
なお,この ReturnType methodName(ArgumentType argumentName, ...) の部分をメソッドのシグネチャと呼びます.
このメソッドの{から} の間をメソッドのボディと呼びます.
そして,メソッドのシグネチャとメソッドのボディを書くことをメソッドを定義する,もしくはメソッドを宣言すると言います.
メソッドは細かく分けましょう. 目安として,メソッド内の処理が5行以上になれば,別のメソッドに分けることを考えましょう.
引数, 返り値のないメソッド
引数も帰り値もないメソッドは,フィールドの値を利用したり, フィールドに値を作成したり,常に同じ値を返すような場合に用いられます.
例えば,次のコードは呼び出された時点の日付,時刻で,フィールドのcurrentDateの値を更新します.
import java.util.Date;
public class DateSample{
Date currentDate = new Date();
// ... today 以外のメソッドは省略.
void updateCurrentDate(){
this.currentDate = new Date();
}
}引数あり, 返り値のないメソッド
引数の値を用いて,何らかの処理を行うメソッドの場合に用いられます.
処理の結果は,System.out.printで出力されたり,フィールドに代入することが多いです.
引数なし, 返り値のあるメソッド
String型の文字列を全て大文字にする toUpperCase や全て小文字にする toLowerCase などのように,
フィールドの値を外に返すために用いられることが多いです.
例えば,次のメソッドは常に,呼び出し時点の時刻を返します.
import java.util.Date;
public class DateSample{
// ... today 以外のメソッドは省略.
Date today(){
return new Date();
}
}引数, 返り値のあるメソッド
値を引数として受け取り,何らかの加工を行った結果を返す場合が多いです.
例題2
前回の練習問題1. HelloWorld改をメソッドを使って書き換えてください.
クラス名は,HelloWorld2r としてください.
コマンドライン引数で与えられた人に,挨拶しましょう.
もし,コマンドライン引数に何も指定されない場合は,
"Hello, World" と出力してください.
以下のプログラムのコメント部分に何を書けば良いかを考えましょう.
public class HelloWorld2r{
void run(String[] args){
if(/* 条件は前回の練習問題と同じ */){
this.greet(/* 引数に何を渡すか考えてください. */);
}
else{
this.greet(/* 引数に何を渡すか考えてください. */);
}
}
void greet(/* 引数となる変数を宣言してください */){
System.out.printf("Hello, %s%n",
/* ここに何が来るでしょうか. */);
}
}なお,runメソッドが実行している実体と同じ実体に対してメソッドを呼び出すときは,
上記のように,thisというキーワードに対してメソッドを呼び出します.詳細は暗黙の定数thisで説明しています.
例題3
前回の練習問題3. 階乗をメソッドを使って書き換えてください.
public class Factorial2{
void run(String[] args){
Integer number = Integer.valueOf(args[0]);
Integer factorial =
this.factorial(/* 引数に何を渡すか考えてください */);
// number の階乗を計算する.
System.out.printf("%d! = %d%n", number, factorial);
}
// ここに factorialメソッドを定義してください.
}メソッドの仮引数と実引数
メソッドの引数を理解するために,仮引数と実引数の2つの概念があることを区別しましょう. 仮引数(parameter)とは,メソッド定義時に定義される引数のことを指し, 実引数(argument)は,メソッド呼び出し時に用いる引数のことである.
次の例で考えてみましょう.
public class LeapYear{
void run(String[] argsForRun){
for(Integer i = 0; i < argsForRun.length; i++){
Integer year = Integer.valueOf(argsForRun[i]);
Boolean leapYearFlag = this.isLeapYear(year);
this.printLeapYear(year, leapYearFlag);
}
}
Boolean isLeapYear(Integer y){
return y % 400 == 0 && (y % 100 != 0 || y % 400 == 0);
}
void printLeapYear(Integer y2, Boolean lyf){
if(lyf)
System.out.printf("%d年はうるう年です.%n", y2);
else
System.out.printf("%d年はうるう年ではありません.%n", y2);
}
}このプログラムには,実引数,仮引数がそれぞれ存在します.
仮引数は,2行目 runメソッド宣言時の String型配列のargsForRunと
9行目のisLeapYearメソッドのInteger型のy,そして,12行目の
printLeapYearメソッドの y2,lyfです.
ここは変数を宣言している部分です.
一方の実引数は,5行目の this.isLeapYearメソッドに渡している変数year,
そして,6行目のthis.printLeapYearに渡している yearと,leapYearFlagです.
これらは既に宣言された変数を利用する箇所です.
仮引数は変数を定義する場所,実引数は,既に宣言された変数を利用する場所であると区別しましょう. ですから,実引数の場所で変数を宣言することはできません.
メソッド内での値の更新
次のプログラムを考えてみましょう.updateDate1,updateDate2,updateDate3の3つのメソッドそれぞれで
年に1を追加しているように見えます.
動作結果を考えて実行してみましょう.
また,なぜこのような結果になるのかを考えてみましょう.
public class DateConfusion{
void run(){
Date date;
date = new Date();
this.updateDate1(date);
System.out.println(date);
date = new Date();
this.updateDate2(date);
System.out.println(date);
date = new Date();
Date date2 = this.updateDate3(date);
System.out.println(date);
System.out.println(date2);
}
void updateDate1(Date d){
d.setYear(d.getYear() + 1);
}
void updateDate2(Date d){
d = new Date(d.getYear() + 1, d.getMonth(),
d.getDate());
}
Date updateDate3(Date d){
// dの時刻と同じ時刻の Date 型の実体が作成される.
Date d2 = new Date(d.getYear() + 1, d.getMonth(),
d.getDate());
return d2;
}
// mainメソッドは省略.
}メソッドの中で参照先が変わったとしてもメソッドの呼び出し元では全く影響を受けません.
一方で,メソッドの中で参照先の実体の状態を変更すると,呼び出し元にも影響を受けます.=
による代入は参照先の変更であることをしっかりと理解しましょう.
変数のスコープ
変数はスコープ(scope)を持っています.スコープとは変数の有効範囲のことです.
有効範囲の外からその変数の参照,代入は行えません.
プログラム中の { と } で囲まれた範囲をブロックと呼びます.
スコープは変数が宣言されて以降,宣言されたブロック内でのみ有効です.
以下のプログラム(メソッドの仮引数と実引数の再掲)の各メソッド内の変数のスコープを確認してみましょう.
まず,runメソッドの仮引数である argsForRun のスコープを確認しましょう.
仮引数はそのメソッド内で有効で,メソッドの外側では例え同じ名前であっても違う変数として扱われます.
次に,ループ制御変数であるi,ループ内で宣言された Integer型の year,
最後に,Boolean型のleapYearFlagのスコープを確認してください.
引き続き,isLeapYearメソッドの仮引数であるyとprintLearpYearメソッドの仮引数y2とlyfのスコープを確認してください.
図をクリックすると,それぞれの有効範囲が出てきます.
このように,変数の有効範囲は基本的には,{から}の範囲内で,宣言されて以降ということがわかると思います.
変数の有効範囲はできるだけ短く,というのが定石です.
有効範囲が広いと,どこからでもアクセスできて,どこで更新されているのかがわからなくなるので,避けるべき,ということです.Q&Aの変数のスコープが短い方が良いのはなぜですか?も参考にしてください.
暗黙の定数this
各メソッドでは,暗黙的に宣言されたthisという変数が利用できます.
thisは,その型の実体が代入されています.
例えば,次の例で考えてみましょう(メソッドの仮引数と実引数の再掲).
public class LeapYear{
void run(String[] argsForRun){
for(Integer i = 0; i < argsForRun.length; i++){
Integer year = Integer.valueOf(argsForRun[i]);
Boolean leapYearFlag = this.isLeapYear(year);
this.printLeapYear(year, leapYearFlag);
}
}
Boolean isLeapYear(Integer y){
return y % 400 == 0 && (y % 100 != 0 || y % 400 == 0);
}
void printLeapYear(Integer y2, Boolean lyf){
if(lyf)
System.out.printf("%d年はうるう年です.%n", y2);
else
System.out.printf("%d年はうるう年ではありません.%n", y2);
}
public static void main(String[] args){
LeapYear ly = new LeapYear();
ly.run(args);
}
}mainメソッドでLearpYearをnewして実体を作成し,lyという変数に代入しています.
そして,lyのrunメソッドを20行目で呼び出しています(ly.run(args);の行).
この呼び出しにより,runメソッド内に処理が移ります.
このrunの中でLeapYearの実体を参照する変数がthisです.
5行目と6行目で this が使われており,this.isLearpYear(year)でからisLeapYearメソッドを,
this.printLeapYear(year, leapYearFlag)でprintLeapYearメソッドをそれぞれ呼び出しています.
なお,thisに値を代入しようとするとコンパイルエラーが発生します.
void run(String[] argsForRun){
LeapYear ly = new LeapYear();
for(Integer i = 0; i < argsForRun.length; i++){
Integer year = Integer.valueOf(argsForRun[i]);
Boolean leapYearFlag = ly.isLeapYear(year);
ly.printLeapYear(year, leapYearFlag);
}
}例えば,runを上のようにし,thisの代わりにly経由でisLeapYearなどを呼び出したとしても,同じ結果を得られます.
ただし,lyとthisは異なる実体です(ly != this).
このようなとき,lyを改めて作成し直す必要はありません.
mainメソッドのlyとrunメソッド内のthisは同じ実体ですが,
mainメソッドとrunメソッドのlyは異なる実体であることに注意しましょう.
練習問題
1. 与えられた文字列のソート
以下の条件を満たすように,コマンドライン引数 で与えられた複数の文字列をソートして出力するプログラムを作成してください (ソートアルゴリズムを自分で書く必要はありません).
- 配列を画面に出力するための
printArrayを実装してください.printArrayで接頭辞を出力できるようにしてください.
- ソートの前後で,
printArrayメソッドを使って配列の要素を出力してください. - クラス名は,
ArgsSorterとしてください.
ソートは Arraysに対して,sortメソッドを呼び出すと渡した配列の要素がソートされます.
ただし,Arraysを利用する場合は,import java.util.Arrays;という一文が
クラス宣言の前に必要です.
public class ArgsSorter{
void run(String[] args){
// ここで,printArray を呼び出し,"before"の一行を出力する.
// argsの内容をソートするため,Arrays.sortメソッドを呼び出す.
Arrays.sort(args); // <= args がソート済みになる.
// ここで,printArray を呼び出し,"after"の一行を出力する.
}
// printArrayメソッドをここに書く.
// mainメソッドは省略.
}実行例
$ java ArgsSorter one two three four five six
before: one, two, three, four, five, six,
after: five, four, one, six, three, two,
$ java ArgsSorter time flies like an arrow
before: time, flies, like, an, arrow,
after: an, arrow, flies, like, time,
$ java ArgsSorter 2016 10 6
before: 2016, 10, 6,
after: 10, 2016, 6,このように,アルファベット順にソートされていることが確認できます.
最後の例は,間違いではありません."2016","10","6"という文字列でソートしている,つまり,
1桁目の文字("2", "1", "6")でソートしているので,この順で正しいです.
2. 学生証番号の正当性を検証するプログラム
本学の学生証番号(6桁)は,各桁を足し合わせると,10の倍数となるようになっています.
コマンドライン引数で与えられた学生証番号が正しいものであるかを判定するプログラムStudentIdValidatorを作成してください.
正しい学生証番号であれば,与えられた学生証番号と共に,valid,正しくない学生証番号であれば,invalid,
与えられた文字列が6桁の学生証番号でなければ,not student idと出力してください.
String型のIdを受け取るvalidateメソッドを定義してください.Integer型のIdを受け取るvalidateIdメソッドを定義してください.
それぞれのメソッドの処理は次の通りです。 この処理通りにしなければいけないわけではありませんが、 このような処理にすると各メソッドの役割がわかりやすいと思います。
runメソッド- 引数で受け取った
String型配列の各要素を順にvalidateメソッドに渡す。
- 引数で受け取った
validateメソッド- 引数で受け取った
String型の値の長さを確認する。- 6桁の学生証番号でなければ、
not student idと出力して終了する。
- 6桁の学生証番号でなければ、
- 学生証番号であれば、
String型をInteger型に変換する。 - 変換後の
Integer型の変数をvalidateIdメソッドに渡す。
- 引数で受け取った
validateIdメソッド- 引数で受け取った
Integer型変数の正当性を確認する。
- 引数で受け取った
ヒント:文字列の長さを取得する.
文字列の長さを取得するには,String型の変数に対して,length()メソッドを呼び出してください.
String name = "Haruaki Tamada";
Integer length1 = name.length(); // 14が代入される.
Integer length2 = "Haruaki Tamada".length(); // length1 と同じく 14 が代入される.上記のように,String型の変数に対して length メソッドを呼び出すとその文字列の長さが取得できる.
同様に String型の値("Haruaki Tamada")に対してもlengthメソッドが呼び出せる.
実行例
$ java StudentIdValidator 024509
024509: valid
$ java StudentIdValidator 123456
123456: invalid
$ java StudentIdValidator 123455 123456
123455: valid
123456: invalid
$ java StudentIdValidator 123509 244409 1024509
123509: valid
244409: invalid
1024509: not student id3. 二次方程式の解
2次方程式の解を解の公式を用いて求めるプログラム作成してください. その際,実数解,重解,虚数解を区別して出力するようにしましょう. ただし,以下の条件を満たしてください.
- クラス名は,
QuadraticEquationとすること. - $ax^2+bx+c$の$a$,$b$,$c$はコマンドライン引数から受け取ること.
-
$a$, $b$, $c$ は
Double型の値が与えられるものとする. runメソッドは以下の通りとし,以下の内容に合うようメソッドを定義すること.
void run(String[] args) {
Double a = convertToDouble(args[0]);
Double b = convertToDouble(args[1]);
Double c = convertToDouble(args[2]);
Double d = discriminant(a, b, c); // 判別式によりDの値を求める.
solve(a, b, c, d); // 判別式から実数解,重解,虚数解に場合分けして結果を出力する.
}String型からDouble型への変換
文字列からDouble型に変換するには,Double.valueOf(stringValue) を利用する.
- 文字列から
Integer型への変換も参照のこと.
// Double value = "3.141592";
// => 文字列は Double 型にそのまま代入できない.
Double value = Double.valueOf("3.141592");
// => 文字列の "3.141592" を Double型に変換して代入する.平方根
平方根は,次のコードで求められます.
Integer two = 2;
Double sqrtOfTwo = Math.sqrt(two); // => 1.41421356...ヒント
Double型の数値をprintfで出力するときは,%lfではなく,%fもしくは%gで出力しましょう.%fは実数型の数値(Double,Float)が出力できます.%gも実数型の数値ですが,最適表示となります.
NaNが出力された場合,0除算,もしくはMath.sqrtに負の値で計算しようとしています.NaNは Not a Number の略です.
- 虚数解の場合,実部と虚数部は別々に計算しましょう.
- 実部は,
$-\frac{b}{2a}$,虚数部は $\pm\frac{\sqrt{-D}}{2a} i$ のように計算しましょう.
-
判別式$D$に $-1$ を掛けているのは,平方根を求めるメソッド(
Math.sqrt)に負数を渡すと結果がNaNになるためです.
-
判別式$D$に $-1$ を掛けているのは,平方根を求めるメソッド(
- 実部は,
$-\frac{b}{2a}$,虚数部は $\pm\frac{\sqrt{-D}}{2a} i$ のように計算しましょう.
実行例
$ java QuadraticEquation 1 -4 4
answer = 2.000000
$ java QuadraticEquation 1 -4 8
answer = 2.000000 + 2.000000 i, 2.000000 - 2.000000 i
$ java QuadraticEquation 1 0 -4
answer = 2.00000, -2.000004. モンテカルロ法による $\pi$の計算
モンテカルロ法により,$\pi$を計算しましょう. モンテカルロ法とは,乱数を用いて統計計算を行う方法です. ここでは,$\pi$を求めます.次のアルゴリズムに従って実装してください.- 0.0〜1.0の乱数を2つ取得し,$x, y$とします.
- 原点と$(x, y)$の距離を計算します.距離は,$\sqrt{x^2+y^2}$で求められます.
- 計算した距離が1よりも小さい場合,ヒットしたとみなします
- 距離が1以下の場合,点は円の内側に存在します(ヒットした).
- 距離が1よりも大きな場合,点は円の外側に存在します.
- 1〜3を規定回数繰り返します.
- ヒットした回数の割合を計算します.
- この割合が$\frac{\pi}{4}$になるので,4を掛け$\pi$を計算します.
クラス名は,MonteCarloPiとします.
引数に値が指定された場合,その値だけ繰り返してください.
値が何も指定されない場合は,1,000回の繰り返しとします.
実行例
計算結果は必ずしもこの通りではありません.
$ java MonteCarloPi
pi = 3.10800
$ java MonteCarloPi
pi = 3.17200
$ java MonteCarloPi 10000
pi = 3.13916
$ java MonteCarloPi 100000
pi = 3.14776
$ java MonteCarloPi 1000000
pi = 3.14127
$ java MonteCarloPi 10000000
pi = 3.14229ヒント
5. 台形公式による積分計算を利用した $\pi$の計算
台形公式を用いて$\pi$を計算してください. まず,半径1の円を表す式$x^2+y^2=1$を考えましょう. 式を変形すると,$y=\sqrt{1−x^2}$となります. これの$0\leq x \leq 1$の範囲を考えます. 下図のように,この式で描かれるグラフに内接するいくつかの台形を考えます. 下図を1度クリックすると,2つの台形(1つは三角形のように見えますが,上底(右側の高さ)が0に近い台形と考えてください)があり, 横幅は$x_i−x_{i−1}$,高さは$h_{i−1}, h_i$です($h_{i−1}\ge h_i$). このとき,一つの台形の面積は,$\frac{(x_i−x_{i−1})\times(h_{i−1}+h_i)}{2}$です. 高さを求めるには,$y=\sqrt{1−x^2}$にその時の$x_i$の値を代入することで求められます.
クラス名は, TrapezoidalRulePiとします.
作成する際に,コマンドライン引数で台形の横幅を指定できるようにしてください.
もし,コマンドライン引数で値が指定されなければ,0.0001が指定されたものとしてください.
実行例
計算結果は必ずしもこの通りでなくても構いません (多少の計算誤差があったとしても構いません).
$ java TrapezoidalRulePi 0.01
pi = 3.1375956845831103
$ java TrapezoidalRulePi 0.001
pi = 3.1414660465553967
$ java TrapezoidalRulePi
pi = 3.141591477698228
$ java TrapezoidalRulePi 0.0000001
pi = 3.141592654152668ヒント
まとめ
まとめ
- Java言語の基礎3
- メソッドの基本的な呼び出し方
- メソッドの定義方法
- 必ずクラス宣言の内側でなければならない.
- 他のメソッドの中には定義できない.
- メソッドの名前が決められる.
- 引数と返り値の型が定義できる.
- メソッドの名前と,引数,返り値の型をまとめてシグネチャと呼ぶ.
- メソッドは5行以内を目指そう.
- メソッドの仮引数と実引数
- メソッドの定義部分に書く引数は仮引数.
- 新たに変数を定義する.
- メソッドの呼び出し部分に書く引数は実引数.
- すでに定義された変数を利用する.
- メソッドの定義部分に書く引数は仮引数.
- メソッド内での値の更新
=は参照先を変更する演算子.- メソッド内で参照先を変更しても,メソッドの呼び出し元では影響を受けない.
- メソッド内で参照先の実体の状態を変更すると,メソッドの呼び出し元でも変更されたまま.
- メソッド内で新たなオブジェクトを作って,それを
returnすると,呼び出し元でも利用できる.
- 変数のスコープ
- 変数が利用できるのは,宣言されて以降.
- 一番内側の閉じ括弧まで.
- 開き括弧(
{)から閉じ括弧(})までをブロックと呼ぶ.
- 暗黙の定数
this- 各メソッド内では,暗黙の定数である
thisを参照できる. thisはそのクラスの実体であり,当該メソッドの持ち主の実体を表す.
- 各メソッド内では,暗黙の定数である
- その他
- 配列のソートは
Arrays.sort(...)メソッドを利用する.import java.util.Arrays文が必要.
- 平方根を求める
Math.sqrtメソッドで求められる.
- 文字列の長さの取得
stringValue.length()で文字列の長さが得られる.- 配列の長さと区別すること.
- 文字列から
Double型への変換方法- 文字列からInteger型への変換も参照のこと
- 配列のソートは