メソッド(関数)
基本的な呼び出し方.
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_AP
String
型の値を大文字に変更するには,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
は異なる実体であることに注意しましょう.