ITエンジニアのプレイングマネージャー化応援サイト

8.クラスメソッド

この記事では、弊社の新人エンジニア研修の参考にJava8を解説します。

前回は文字と文字列の扱いについて解説しました。

クラスやインスタンスという言葉が出てきて、いよいよオブジェクト指向らしくなっていましたね。

今回はクラスメソッドについて解説します。

まずはそもそもメソッドとは何かというところからお話を始めたいと思います。

メソッドを一言で説明すると一連の処理に名前をつけたものということができます。

例えば、毎朝、①起きて、②顔を洗い、③歯を磨いて、④朝ごはんを食べている、とします。

この4つの動作に「朝のルーティーン」という名前をつけて、毎朝、このルーティンを実行するようにすれば人生楽ですね。

メソッドはこのように一連の処理に名前をつけて呼び出せるようにしたものです。

 

1.メソッドとは

プログラムが長くなると全体の見通しが悪くなることがあります。

プログラム全体で何をやっているのか分からなくなる訳です。

また、あちらこちらで同じコードを書いているときがあります。

そうすると無駄に記述量ばかり増えて、かつ、修正が必要になった際にはすべてのコードを修正しなければならなくなります。

プログラムには繰り返しがないほうが望ましいのです。

DRY(Don’t Repeat Yourself:繰り返しを避けよ)原則としても知られています。

 

命令文を分けた方がプログラムを読んだ人にも分かりやすくなり、後々のメンテナンスも容易になります。

そのような時にメソッドを使います。

methodとは、直訳すれば「方法、やり方」といった意味です。

メソッドはJava以外の言語では関数と呼ばれることもあります。

直線を表す

y = 2x + 1 

という関数のようにxに何かをインプットしたらyがアウトプットされるブラックボックスのようなものと捉えてください。

 

一番シンプルなメソッドの形は、インプット(引数)、アウトプット(戻り値)ともになく、処理だけをするという、以下のようなものです。

<構文>

void メソッド名() {

      命令文;

}

voidは戻り値が“ない”という意味でした。

メソッドの例として、10数える(1~10までの整数を表示する)という処理を持ったcount10()というメソッドを作成してみます。

<結果>

1 2 3 4 5 6 7 8 9 10

mainメソッドについては、この連載の1回目でお話ししました。

プログラムのエントリーポイントでしたね。

mainメソッドでは、

count10();

とだけ書かれており、ここでメソッドを呼び出しています。

以降はメソッドの中に処理が移り、処理が終わるとメインメソッドに処理が戻ります。

ですから、count10メソッドとmainメソッドの位置関係を逆にして、以下のように書いても同じことです。

メソッドには、引数と戻り値があるのでした。

引数とは、メソッドに渡す値のことです。

戻り値とは、その名の通り、メソッドの呼び出し元(上記サンプルプログラムの場合はmainメソッド)に戻す値のことです

命令を実行した結果の値のことです。

上記の例では、引数はなし、戻り値もvoidなのでなし、というわけです。

では、次に引数のあるメソッドを見てみましょう。

 

2.引数のあるメソッド

引数のあるメソッドは以下のような形をしています。

<構文>

void メソッド名(型 変数名) {

      命令文;

}

()カッコの中に型と変数名を書きます。

この変数名を仮の引数という意味で「仮引数」と呼びます。

このメソッドのブロックの中では、この変数名を使って処理を記述することができるようになります。

先のサンプルプログラムに手を加えて、毎回10まで数えあげるのではなく、()内で与えられた任意の数まで数えあげることができるようにしてみます。

<結果>

1 2 3 4 5
1 2 3 4 5 6 7 8 9 10

ここで、count(5)の5やcount(10)の10という()内で与えられる値は実際の引数という意味で「実引数」と呼びます。

仮引数と、実引数は対にして覚えましょう。

 

3.戻り値のあるメソッド

戻り値のあるメソッドは以下のような形をしています。

<構文>

戻り値の型 メソッド名(仮引数列) {

      命令文;

  retrun 戻り値;

}

ポイントは、以下の3点です。

1.戻り値の型をメソッド名の前に書きます。

2.return文を使って戻り値を戻します。

3.最終的に戻すことができる値は1つだけです。

例として、一辺の長さを与えると正方形の面積を返すメソッドgetAreaOfSquereを作成してみましょう。

※なお、クラス名やメソッド名で単語の連なりを使いたい場合は、上記のように2つ目以降の単語の頭文字を大文字にします。このような命名方法をラクダの形に見立ててキャメルケース(camel case)といいます。

良い名前付けは本当に大切です。

codic: プログラマーのためのネーミング辞書

を是非活用してください。

 

結果

14.44

先ほど、

3.最終的に戻すことができる値は1つだけです。

と書きました。

この意味は、if文などの条件分岐でreturn文は複数あっても、最終的には戻り値は一つに決まるという意味です。

サンプルプログラムで確認してみます。

与えられた引数が偶数かどうかを判定するisEvenというメソッドです。

return文が2つありますが、if文によってどちらか一方しか実行されないため問題ありません。

<結果>

false

なお、参考までに上記のプログラムは簡略化して以下のように書くこともできます。

static boolean isEven(int num) {
   return num % 2 == 0;
}

戻り値のboolean型はtrueかfalseのいずれかの値を持つものであればOKだからです。

 

4.メソッドのオーバーロード

ここまで学んできて、あれ?と思った方はいませんか?

Javaは型にうるさい言語なのに、、、実引数の型を気にしたことが無かったことに。

例えば、次のサンプルプログラムを見てください。

おなじみのprintlnメソッドです。

渡している実引数の型はそれぞれ、String,char,int,doubleですね。

では、printlnメソッドの定義はどうなっているのでしょうか?

IDEをお使いの方はコントロールキーを押しながらprintlnメソッドをクリックしてみてください。

Javaのソースコードに飛びますが、4つのprintlnメソッドのうちのどれを押すかによって飛び先が違いますね。

例えば、doubleの場合は、

という定義に飛びます。

文字列の場合は、

です。

仮引数の型が違うだけですね。

メソッド名と戻り値の型が同じで、引数の型や数、並び順が違うメソッドを複数定義することをオーバーロード(overload)といいます

日本語では多重定義と訳されます。

この定義によれば、戻り値の型だけが違っていてもオーバーロードではありません。

public void println(String x)

に対して

public String println(String x)

はオーバーロードではありません。

メソッドの入り口を広げるのがオーバーロードですからね。

 

インプットの幅を広げるオーバーロード

インプットの幅を広げるオーバーロード

 

本来、引数の型や数が違えば別のメソッド定義が必要となるわけですが、

もし、そのためだけに違ったメソッド名を用意しなければならないとすれば大変です。

メソッドを使う側も引数の型や数に気を付けて呼び出す必要が出てきてしまいますね。

しかし、オーバーロードによってそのような負担は無くなるのです。

皆さんも今までprintlnメソッドに渡す引数の型など、気にもしなかったのではないでしょうか?

 

そしてこの仕組みは皆さんも利用することができます。

サンプルプログラムを見てください。

<結果>

引数なしのmethodが呼ばれました。
引数にint型をとるmethodが呼ばれ2を受け取りました。
引数にdouble型をとるmethodが呼ばれ3.14を受け取りました。
引数に文字列型をとるmethodが呼ばれGoodby.を受け取りました。

オーバーロードとは、同じクラスの中でメソッド名と戻り値の型が同じで、引数の型や数、並び順が違うメソッドを複数定義することをいいましたね。

呼び出し時に指定される引数によって実行されるメソッドが区別されるという仕組みでした。

注意点としては、戻り値の型が違っても、それだけではオーバーロードにならないという点です。

①メソッド名、②引数の型、③引数の数の3つの要素をシグネチャと呼びます。

オーバーロードは、①が同じで、②または③が異なる場合と引数の並び順が違う場合に有効なのでした。

メソッドのシグネチャ

メソッドのシグネチャ

なお、シグネチャが全く同じメソッドを複数宣言することはできません。

 

5.メソッドのメリット

メソッドを使うメリットは以下の2点です。

1.定形処理の再利用

2.メッセージ性を高める

以下、順に説明します。

1.定形処理の再利用

先ほどの「10まで数える処理」を3回、メソッドなしに書くとこうなります。

<結果>

1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10 1 2 3 4 5 6 7 8 9 10

しかし、メソッドを使った例であればこうなります。

<結果は省略>

どちらが見やすいかは明らかですね。

また、これまで10まで数えていたものが仕様変更により11まで数えることとなったとすれば、メソッドを利用していなかった場合は3箇所修正しなければならないのに対して、メソッドを利用していた場合は1箇所で済みます。

2.メッセージ性を高める

定型の処理の再利用のほかにもメソッドを使うメリットがあります。

それは、メソッドを使うことでメッセージ性が高まるというものです。

例えば、

if (age >= 20){

    system.out.println(“酒を飲む”);

}

というコードよりも以下のようにisAdultというメソッドを使った方が「成人判定をしている」ということがより分かりやすくなります。

<結果>

酒を飲む

もっともこの例ではメソッドの処理が単純すぎて恩恵を感じられないかもしれません。

実際には以下のようにメソッド無しに書くことも可能ですので。

<結果>は上記を同じ

ただし、もう少しメソッド内の処理が複雑になると、別処理にして名前をつけることの効果が出てきます。

 

では、メソッドを使うことのデメリットは何でしょうか?

それは、プログラムを上から順に追えなくなることです。

これまでのサンプルプログラムでは、メインメソッドの中を上から順にみていけばプログラムを読み解けました。

これをアルゴリズムでは順次と呼んでいましたね。

しかし、ここからは処理があちらこちらに飛ぶので慣れないうちは大変だと思います。
※もっとも処理が別クラスに分かれて、クラス間で引数と戻り値をやり取りするようになると、ますます複雑になるのですが。。。

特に次の再帰処理などは初学者が理解困難な部分です。

しかし、理解できればメソッドの動きはよく理解できるようになりますので説明してみましょう。

 

6.メソッドの再帰処理

では、ここで1回目でも見た再帰処理をもう少し実用的な形でやってみましょう。

階乗(factorial)の計算です。

例えば

5!=5*4*3*2*1=120

という計算はみなさんも学生時代に学んだのではないでしょうか?

この計算を再帰を使って解くことを考えます。

ただし、話を単純にするために3!にします。

この計算の過程をたどると以下のようになります。

答えは6になります。

n! = n * (n-1)!

という式になりますから、式の中に自分自身の式がある。

つまり、再帰的になっていますね。

以下のサンプルコードを見て下さい。

factorialメソッドの中でfactorialメソッドを呼んでいます。

ただし、1回目でスタックオーバーフローさせた時とは違い今回は

n <= 1

のとき、1を返して終わりますからスタック・オーバーフローにはなりません。

<結果>

6

このプログラムを実行しているときのメモリのスタック領域の様子を図示すると以下のようになります。

 

階乗の計算をしているときのスタック領域のイメージ

階乗の計算をしているときのスタック領域のイメージ

 

※上記の図で文字色が薄くなっている部分は実行されないコードです。

①→②→③と3回メソッドが呼ばれていって、1になると今度は④→⑤→⑥とreturn文で戻っていって答えが求められる様子がイメージできますか?

 

7.メソッドチェーン

メソッドの戻り値に対してメソッドをつなげていく書き方を今後、目にすることがあると思います。

メソッドチェーン(method chain)といいます。

具体例を見ましょう。

以下のサンプルプログラムでは、文字列”hello”を大文字にしたうえでその先頭から4文字を取り出してコンソール出力しています。

<結果>

HELL

左から順に処理されて、

“hello”.toUpperCase()

によって、戻り値の”HELLO”を得、

“HELLO”.subSequence(0, 4)

によって、戻り値の”HELL”を得ています。

メソッドチェーンは、あまり多用すると読みにくいコードになるので注意が必要ですが、便利な書き方なのでしばし使われます。

 

ちなみに、このときの2つの引数、”beginIndex”と”endIndex”の考え方は以下のとおりです。

インデックスの考え方

インデックスの考え方

 

なお、参考までにtoUpperCaseやtoLowerCase(小文字にする)というメソッドは、大文字小文字関係なく文字列を扱いたい場合、例えば文字列を大文字・小文字関係なく検索したい場合などに有効なメソッドです。

 

7.staticキーワードの意味

クラスメソッドにはstaticキーワードがついていました。

staticは静的という意味です。

つまり、staticメンバは実行時にクラスがロードされた時、一度だけメモリー上にコピーされるのです

以下の図のようにstaticメンバは保存されるメモリ上の領域も今まで出てきたスタック領域やヒープ領域とは違うのです。

メモリの3つの領域のイメージ

メモリの3つの領域のイメージ

一方インスタンスはプロジェクトの実行中に必要な時に必要なだけヒープ領域に作られるというのが大きな特徴でした。

つまり必要な時になって初めてメモリにロードされるのです。

これを動的(dynamic)に作ると表現することもあります。

静的(static)と対にして理解しておきましょう。

 

<まとめ:隣の人に正しく説明できたらチェックを付けましょう>

□一連の処理に名前をつけたものをメソッドといい、アウトプット(戻り値)、メソッド名、インプット(引数)、処理内容からなる
 
□メソッド名と戻り値の型が同じで、引数の型や数、並び順が違うメソッドを複数定義することをオーバーロードという
 
□メソッドを使うことで定形処理の再利用ができ、メッセージ性を高めることができる
 
□staticメンバは実行時にクラスがロードされた時、一度だけメモリー上にコピーされる
 

 

まとめができたら、アウトプットとして演習問題にチャレンジしましょう。

問題8.クラスメソッド

 

今回はクラスメソッドについて見てきました。

次回のテーマはンスタンスの活用です。

いよいよ、オブジェクト指向らしいプログラムになってきます。

クラスを元にインスタンスを作成してそれぞれのインスタンスのメソッドを使う方法について学びましょう。

 

【今回の復習Youtube】

032-アルゴリズム-最大値を求めるプログラム

033-アルゴリズム-線形探索

034-アルゴリズム-二分探索(訂正あり)

035-アルゴリズム-バブルソート

036-アルゴリズム-選択ソート

037-アルゴリズム-挿入ソート

 

JavaSE8の解説に戻る

PAGETOP
Copyright © Say Consulting Group, Inc. All Rights Reserved.