Print Friendly, PDF & Email

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

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

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

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

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

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

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

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

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

 

1.メソッドとは

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

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

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

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

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

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

 

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

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

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

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

直線を表す

y = 2x + 1

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

 

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

<構文>

void メソッド名() {

      命令文;

}

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

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

package chap08;

public class Example01 {

    static void count10() {
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
    }

    public static void main(String[] args) {
        count10();
    }
}

<結果>

1 2 3 4 5 6 7 8 9 10

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

プログラムのエントリーポイント、つまり開始点でしたね。

mainメソッドでは、

count10();

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

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

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

package chap08;

public class Example02 {

    public static void main(String[] args) {
        count10();
    }

    static void count10() {
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
    }
}

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

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

戻り値とは、その名の通り、メソッドの呼び出し元に戻す値のことです

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

呼び出し元とは上記サンプルプログラムの場合はmainメソッドのことです。

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

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

 

2.引数のあるメソッド

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

<構文>

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

      命令文;

}

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

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

なぜなら、実際に渡される数値はまだ決まっていないからです。

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

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

package chap08;

public class Example03 {

    static void count(int num) {
        for (int i = 1; i <= num; i++) {
            System.out.print(i + " ");
        }
    }

    public static void main(String[] args) {
        count(5);
        System.out.println();
        count(10);
    }
}

<結果>

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

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

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

ここでたとえば、実数(5.0や10.0)を渡してみるとどうなるか?

実験してみてください。

 

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

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

<構文>

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

      命令文;

retrun 戻り値;

}

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

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

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

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

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

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

良い名前付けは本当に大切です。codic: プログラマーのためのネーミング辞書を是非活用してください。

package chap08;

public class Example04 {

    public static void main(String[] args) {
        double area = getAreaOfSquere(3.8);
        System.out.println(area);
    }

    static double getAreaOfSquere(double length) {
        return length * length;
    }
}

結果

14.44

先ほど、

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

と書きました。

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

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

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

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

package chap08;

public class Example05 {

    public static void main(String[] args) {
        System.out.println(isEven(71));
    }

    static boolean isEven(int num) {
        if (num % 2 == 0) {
            return true;
        } else {
            return false;
        }
    }
}

<結果>

false

 

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

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

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

 

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

オーバーロードを使うと同じメソッド名で引数の数や型、並び順が違うメソッドを定義できます。

オーバーロードのイメージです。

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

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

 

ところで、ここまで学んできて、あれ?と思ったことはありませんでしたか?

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

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

package chap08;

public class Example06 {

    public static void main(String[] args) {
        System.out.println("Hello");
        System.out.println('A');
        System.out.println(256);
        System.out.println(3.14);
    }
}

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

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

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

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

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

例えば、doubleの場合は、

 public void println(double x) {

という定義に飛びます。

文字列の場合は、

  public void println(String x) {

です。

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

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

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

 

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

public void println(String x)

に対して

public String println(String x)

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

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

 

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

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

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

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

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

 

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

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

package chap08;

public class Example07 {

    public static void method() {
        System.out.println("引数なしのmethodが呼ばれました。");
    }

    public static void method(int i) {
        System.out.println("引数にint型をとるmethodが呼ばれ" + i + "を受け取りました。");
    }

    public static void method(double d) {
        System.out.println("引数にdouble型をとるmethodが呼ばれ" + d + "を受け取りました。");
    }

    public static void method(String s) {
        System.out.println("引数に文字列型をとるmethodが呼ばれ" + s + "を受け取りました。");
    }

    public static void main(String[] args) {
        method();
        method(2);
        method(3.14);
        method("Goodby.");
    }
}

<結果>

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

 

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

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

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

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

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

メソッドのシグネチャ

メソッドのシグネチャ

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

このシグネチャのお話は、また、オーバーライド(override)のところでも出てきます。

オーバーロードとオーバーライド、2文字違いですが全く違う概念です。

また、お話しします。

 

5.メソッドのメリット

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

1.定形処理の再利用

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

以下、順に説明します。

1.定形処理の再利用

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

package chap08;

public class Example08 {

    public static void main(String[] args) {
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
    }
}

<結果>

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

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

package chap08;

public class Example09 {

    static void count10() {
        for (int i = 1; i <= 10; i++) {
            System.out.print(i + " ");
        }
    }

    public static void main(String[] args) {
        count10();
        count10();
        count10();
    }
}

<結果は省略>

mainメソッドの流れが見やすいのはどちらかは明らかですね。

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

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

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

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

例えば、

if (age >= 20){

    system.out.println("酒を飲む");

}

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

package chap08;

public class Example10 {

    public static void main(String[] args) {
        int age = 23;
        
        if (isAdult(age)) {
            System.out.println("酒を飲む");
        }
    }

    private static boolean isAdult(int age) {
        return age >= 20;
    }
}

<結果>

酒を飲む

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

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

package chap08;

public class Example11 {

    public static void main(String[] args) {
        int age = 23;
        
        boolean isAdult = age >= 20;

        if (isAdult) {
            System.out.println("酒を飲む");
        }
    }
}

<結果>は上記を同じ

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

 

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

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

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

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

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

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

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

 

6.メソッドの再帰処理

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

再帰とは、再び帰ると書きますが、再び自分自身に戻る式のことです。

例えば、階乗(factorial)の計算です。

例えば

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

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

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

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

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

3! = 3 * 2!
         2! = 2 * 1!
                  1! = 1
この計算を下から上にたどると
1! = 1
2! = 2
3! = 6

答えは6になります。

n! = n * (n-1)!

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

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

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

package chap08;

public class Example12 {

    static int factorial(int n) {
        if (n <= 1) {
            return n;
        }
        return n * factorial(n - 1);
    }

    public static void main(String[] args) {
        int ans = factorial(3);
        System.out.println(ans);
    }
}

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

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

n <= 1

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

<結果>

6

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

ちなみに、"stack"とは「積み上げる」という意味で、メソッドはこの領域にロードされるのでしたね。

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

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

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

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

 

7.メソッドチェーン

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

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

メソッドの鎖という意味ですね。

具体例を見ましょう。

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

package chap08;

public class Example13 {

    public static void main(String[] args) {
        System.out.println("hello".toUpperCase().subSequence(0, 4));
    }
}

<結果>

HELL

左から順に処理されて、

"hello".toUpperCase()

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

"HELLO".subSequence(0, 4)

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

"hello"という文字列は実体(インスタンス)なのでメソッドを持っているのですね。

(例えば、プリミティブの1の後ろに.をつけても何も起こりません)

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

 

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

インデックスの考え方

インデックスの考え方

 

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

 

7.staticキーワードの意味

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

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

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

このあとstaticがついていないメソッドも出てくるので、区別しましょう。

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

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

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

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

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

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

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

 

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

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

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

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

 

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

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

 

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

問題8.クラスメソッド

これまではメインメソッドに全ての処理を書いてきました。   しかし、そのようなプログラムはJavaの本来のプログラムではありません。   ここからは複数のメソッドをあわ…

【今回の復習Youtube】
 

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

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

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

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

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

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

 

JavaSE8の解説に戻る