WisdomSoft - for your serial experiences.

4.3 メソッド

オブジェクトが持つ動作(振る舞い)をメソッドと呼びます。メソッドに書かれた手続きはオブジェクトに関連付けられ、オブジェクト自身が持つメンバにアクセスできます。

4.3.1 オブジェクトの動作

これまでも main() メソッドをクラス内に記述してきましたが、そもそもメソッドとは何かということについては詳しく説明していませんでした。メソッドとは、オブジェクトの動作を定義したもので、オブジェクト指向全般では操作と呼ばれます。いわば、メソッドはクラスが提供するサービスです。

メソッドは、何らかの処理を実行するための文を集めたブロックです。外部からメソッドを呼び出すことによって、そのメソッド内のコードを実行することができるのです。頻繁に用いられる計算処理などをメソッドとして提供することで、効率的なプログラムを作成することができるのです。従来の手続き型言語は、サブルーチンや関数と呼ばれるコードのまとまりを単純に呼び出すだけでしたが、オブジェクト指向のメソッドという概念は、これがインスタンスごとに割り当てられる点で、独立性の高いものなのです。

仮想マシンが呼び出す main() メソッドはインスタンスに関連付けられていません。しかし、メソッドをクラスに関連付けることによって、オブジェクトごとのフィールドを制御することができるようになります。メソッドを作成するにはメソッド宣言を記述しなければなりません。

メソッド宣言
戻り値型 メソッド名(仮パラメータリスト) { メソッド本体 }

メソッドは、何らかの値を受け取ったり、処理結果を呼び出し元に返したりすることができます。メソッド名の前には戻り値型を指定します。値を返すメソッドは、この型に互換性のある値を呼び出し元に返すことができます。また、仮パラメータリストを記述することで、呼び出し元から何らかの情報を受け取ることができます。これらの方法はすぐ後に解説するとして、今は値を受け取らず、戻り値を返さない最も単純なメソッドを作って、メソッドの働きを調べてみましょう。

class クラス名 {
	void メソッド名() {
		...
	}
}

メソッドは、必ず何らかのクラスのメンバとして記述されます。メソッド名の前には、そのメソッドが返す値の型を指定しなければならないのですが、結果を返さない場合は void を指定します。作成したメソッドを呼び出すことによって、制御がメソッド内のブロックに移行します。

メソッドを呼び出すには、メソッド起動式を用います。メソッド起動式の考え方は、基本的にフィールド・アクセス式と同じです。メソッドを保有するオブジェクトが存在しなければ、メソッドを呼び出すことはできません。

メソッド起動式
オブジェクト . メソッド名(引数リスト)

ドット記号 . の左側には目的のメソッドを保有するオブジェクトを、メソッド名は呼び出すメソッドの識別子を指定します。引数リストには、メソッドが仮パラメータを宣言している場合、メソッドに情報を渡すために指定します。例えば、これまで println() メソッドに渡してきた文字列や変数などがこれにあたります。

メソッドを呼び出すと、プログラムの制御はそのメソッドに移行します。そして、メソッドの本体であるブロック内のコードを実行し、ブロックが終了した時点でメソッドの呼び出し元に復帰する仕組みになります。このような性質を持っているため、何段もメソッドの呼び出しが重なっても、最終的には元の位置に戻ってくることが保証されるのです(強制終了時などは除く)。

コード1
class Color {
	int r , g , b;
	void showColor() {
		String str = "R = " + r + " : G = " + g + " : B = " + b;
		System.out.println(str);
	}
}

class Test {
	public static void main(String args[]) {
		Color color1 = new Color();
		Color color2 = new Color();

		color1.r = 0xFF;
		color1.g = 0x50;
		color2.r = color2.g = color2.b = 0xFF;
		
		color1.showColor();
		color2.showColor();
	}
}
実行結果
>java Test
R = 255 : G = 80 : B = 0
R = 255 : G = 255 : B = 255

コード1では、RGB 形式の色情報を表すための Color クラスを作成しています。このクラスでは、赤要素を表す r、緑要素を表す g、青要素を表す b フィールドがそれぞれ宣言されており、値が大きいほど色が強いことを表すものとします。そして、これらの色情報を格納した Color オブジェクトを視覚的に確認できるようにするため、フィールドの値を表示するための ShowColor() メソッドを宣言しています。このメソッドを呼び出すと、オブジェクトのフィールドの値を画面に表示します。

通常、フィールドにアクセスするにはオブジェクトを指定しなければなりませんでしたが、不思議なことに ShowColor() メソッド内では Color クラスのフィールドにアクセスしているのにもかかわらず、オブジェクトを指定していません。str 変数の初期化式に r、g、b のフィールド変数を指定していますが、オブジェクトは存在しません。実は、同一クラスのフィールドやメソッドへのアクセスは、オブジェクトを指定する必要はありません。この場合、ShowColor() メソッドは、ShowColor() メソッドを保有するオブジェクトのフィールドを用いているのです。例えば、color1.ShowColor() では color1 オブジェクトの r、g、b フィールドを表します。

Java 言語では、変数やメソッドへのアクセスで用いられる単一の識別子を単純名と呼び、識別子を保有するオブジェクトなどを指定し、"." トークンに続き識別子を指定する形式を限定名と呼びます。color1.showColor() というメソッド起動式は限定名であり、showColor() メソッド内の r や g といった自分自身へのフィールド・アクセス式は単純名で指定していると考えることができます。

クラスに関連付けられたメソッドでは、呼び出されたメソッドを保有しているオブジェクトを暗黙的に表しているという事実は、コード1の実行結果から確認することができます。color1.ShowColor() の呼び出しで表示された r、g、b フィールドの数値は、color1 オブジェクトのフィールド値であり、color2.ShowColor() メソッドの呼び出しで表示された数値は color2 オブジェクトの値です。メソッドはオブジェクトに強く結び付けられているため、オブジェクトに特化した処理をスマートに実現できることを表しています。C 言語には、このようにデータ型と処理を結びつける方法はありませんでした。

これは、フィールドだけではなく、メソッド起動式にも同じことが当てはまります。同じオブジェクトの他のメソッドを呼び出す場合には、オブジェクトを省略することができます。

コード2
class Test {
	public static void main(String args[]) {
		System.out.println("start main");
		new Test().method1();
		System.out.println("end main");
	}
	void method1() {
		System.out.println("\tstart method1");
		method2();
		System.out.println("\tend method1");
	}
	void method2() {
		System.out.println("\t\tmethod2");
	}
}
実行結果
>java Test
start main
        start method1
                method2
        end method1
end main

コード2は、メソッドの呼び出しと制御の移行関係を確認するためのものです。まず、プログラムを起動すると main() メソッドが呼び出されますが、この時点では何もインスタンスは生成されていません。main() メソッドを含む Test クラスのインスタンスを作成するためにクラス・インスタンス生成式を用い、同時にメソッドを呼び出しています。

new Test().method1() という文は誤りではありません。new Test() はクラス・インスタンス生成式であり、この式はオブジェクトを返します。変数に保存しない以上、このオブジェクトは使い捨てですが、問題はありません。プログラムは生成したオブジェクトの method1() メソッドを即座に呼び出します。

method1() メソッドからは method2() メソッドを呼び出しています。このときオブジェクトを指定していませんが、method1() メソッドはクラスに関連付けられたメソッドなので、暗黙的に自分自身のオブジェクトを指しています。method2() メソッドが正常に終了すると、プログラムは呼び出し元に返ろうとします。実行結果を見てわかるように、method2() の処理が終了すれば method1() に復帰し、method1() が終了すると main() メソッドまで戻っています。

4.3.2 仮パラメータ

値を受け取らず、値を返さないメソッドは内部的な処理しかできないため、特定のコード群をまとめたマクロ(処理を自動化する小さなプログラム)のような働きなど、利用方法は限られます。しかし、呼び出し元から情報を受け取り、それを加工したり、結果を返すことができれば、クラスの可能性は大きくなるでしょう。

メソッドに何らかの情報を与えるには、まず、メソッドがその情報を必要としていることを宣言しなければなりません。これを、仮パラメータと呼びます。仮パラメータとは、メソッドに渡された情報を保存するための変数の宣言のようなものです。仮パラメータは、メソッド宣言の仮パラメータリストで型と変数名を指定します。複数の仮パラメータを指定する場合はカンマ , で区切ります。

仮パラメータリスト
 変数名 ,  変数名 ...

ローカル変数宣言やフィールド宣言とは異なり初期化はできません。仮パラメータの初期化はメソッドの呼び出しをもって行われるためです。例えば、数値型の変数を受けるメソッドは次のように宣言します。

void SetValue(int iValue) { ...

このメソッドを呼び出すには、仮パラメータに指定されている型の情報を渡さなければなりません。メソッド起動式では、パラメータに情報を渡すために引数リストを指定することができます。引数リストとは、式をカンマ , で区切った式の並びを指します。上の SetValue() メソッドを呼び出すには、以下のようなメソッド起動式を記述します。

SetValue(841);

これまで、println() メソッドでは単一のリテラルや変数を渡してきましたが、メソッド呼び出しと同時に引数リストに式を記述することもできるのです。式の結果は仮パラメータの型に対して恒等変換かワイドニング変換で表現できなければなりません。この、メソッド起動時の引数型変換処理をメソッド起動変換と呼びます。

コード3
class Test {
	public static void main(String args[]) {
		Test obj = new Test();
		obj.showValue("Char A" , 'A');
		obj.showValue("10 + 100" , 10 + 100);

	}
	void showValue(String valueName , int value) {
		System.out.println(valueName + " = " + value);
	}
}
実行結果
>java Test
Char A = 65
10 + 100 = 110

コード3から確認できるように、引数リストは仮パラメータリストに対応していなければなりません。ShowValue() メソッドの第1引数には String 型、第2引数には int 型にメソッド起動変換可能な型を渡さなければなりません。ShowValue() メソッド側では、与えられた情報は仮パラメータに代入されているため、valueName や iValue をローカル変数のように扱うことができます。これらの仮パラメータの中に、渡された値が格納されているのです。実行結果を見れば、ShowValue() メソッドの呼び出しに指定した値が適切に処理され、画面に表示されていることを確認できます。

4.3.3 戻り値

メソッドは、処理の結果など、何らかの意味のある情報を呼び出し元に返すことができます。何らかの情報の加工をメソッドにまとめることによって、クラスの利用者はメソッドを呼び出すだけで複雑な処理を省略し、求めていた結果だけをスマートに取得することができます。

メソッドが値を返すには、まずメソッド宣言で返す値の型を指定しなければなりません。これまでは何も値を返さないことを表す void を指定してきましたが、void の代わりに返すべき型を指定することで、その型に代入可能な値を返すことができるようになります。メソッドの呼び出し側は、このメソッドを戻り値型のオペランドとして扱うことができるようになります。

値を返すメソッドは、メソッド終了時に return 文を指定しなければなりません。return 文はメソッドのブロック内であればどこでも指定することが可能です。return 文では return キーワードに続き、メソッドが返す値となる式を指定することができます。この式の結果が、メソッドの戻り値となります。

return文
return 式文;

void 型の戻り値を持つメソッドでは式文を省略することができます。void 型の戻り値のメソッドでは return 文そのものを省略することができ、ブロックの終了がメソッドの終了とされましたが、値を返すメソッドは return 文を省略することができません。因みに、値を返さないメソッドでも return 文を使うことによって明示的にメソッドを終了させることができます。return 文が指定されれば、処理の途中でもメソッドを強制的に終了させるので、何らかの理由でメソッドを終了したい場合にも使うことができます。

コード4
class Test {
	public static void main(String args[]) {
		Test obj = new Test();
		int iValue = obj.max(10 , 100);
		System.out.println(iValue);
		System.out.println(obj.max(1000 , 100));
	}
	int max(int value1 , int value2) {
		return value1 > value2 ? value1 : value2;
	}
}
実行結果
>java Test
100
1000

コード4は、2 つの数値型パラメータ value1 と value2 を受け取り、値が大きい方を返すという単純なメソッドを宣言しています。メソッドの呼び出し元では結果を受けることができるため、変数に代入したり、式の一部としてメソッドを利用することができるようになります。max() メソッドの最初の呼び出しでは数値 10 と 100 を渡し、100 の方が大きいため、メソッドは 100 を返しています。次に 1000 と 100 の値を渡し、1000 が返っていることがわかります。

4.3.4 クラスの実用例

ようやく、フィールドやメソッドの機能を一通り説明しました。フィールドとメソッドはクラスの、そしてオブジェクト指向の基本的な部分であり、最も重要な部分でもあります。

フィールドやメソッドを学習したことによってクラスを構築できるようになりましたが、クラスはオブジェクトのテンプレートです。クラスを構築する時は、どのようなオブジェクトをどのような利用目的で使うのかを十分に検討しなければなりません。例えば、色を表すためのクラスにネットワーク接続機能を与えるのは極めて意味のない行為です。

クラスの役割は必要な範囲で細かく分解する必要があります。例えば色を表すクラスは、色を制御するために必要な機能だけを提供するべきなのです。フィールドやメソッドは、そのクラスの役割に特化したものを提供します。

コード5
class Color {
	int r , g , b;
	void setColor(int color) {
		r = (color & 0xFF0000) >>> 16;
		g = (color & 0x00FF00) >>> 8;
		b = (color & 0x0000FF);
	}
	int getColor() {
		return (r << 16) | (g << 8) | b;
	}
	void showColor() {
		System.out.println("R = " + r + " : G = " + g + " : B = " + b);
	}
}

class Test {
	public static void main(String args[]) {
		Color color = new Color();

		color.setColor(0xFFEE80);
		color.showColor();
		System.out.println("Color = " + color.getColor());
	}
}
実行結果
>java Test
R = 255 : G = 238 : B = 128
Color = 16772736

コード5は、最小限の機能を備えた Color クラスを提供しています。このクラスは色を表す r、g、b 数値型フィールドと、それらのフィールドを 1 つの int 型で入出力するための SetColor() メソッドと GetColor() メソッドを宣言しています。SetColor() メソッドには最下位ビットから 8 ビットずつで順に青、緑、赤の要素を表す値を格納した整数を渡すことができます。メソッドは受け取ったパラメータから整数を分解して、適切な値をフィールドに代入します。逆に GetColor() はフィールドから 1 つの整数値に復元します。これらの基本的な作業をクラスで提供することによって、クラス利用者の負担を軽減することができ、プログラムの生産性を底上げすることができます。

4.3.5 this キーワード

クラスに結び付けられたメソッドなど、インスタンスを保有する文は自分自身のメンバにアクセスするときにオブジェクトの指定を省略することができました。これは、メソッドを起動したオブジェクトを暗黙的に指しているわけですが this キーワードを用いることで明示的に指定することも可能です。

this キーワードを使用できるのは、メソッドやフィールド初期化子など、インスタンスを保有するブロック内の文でなければなりません。インスタンスが存在しない main() メソッドなどでは、this が指すオブジェクトが存在しないため、指定することはできないのです。

明示的に自分自身のフィールドやメソッドにアクセスしていることを表すために this を使う以外にも、フィールドの変数名とメソッド内のローカル変数名や仮パラメータの名前が衝突した時に使うと便利です。実は、フィールドとローカル変数名の衝突は許容されています。この場合、ローカル変数が優先されてしまうのですが、this を使うことでフィールドにアクセスできるようになるのです。

コード6
class Color {
	int r , g , b;
	void setColor(float r , float g , float b) {
		this.r = (int)(0xFF * r);
		this.g = (int)(0xFF * g);
		this.b = (int)(0xFF * b);
	}
	void showColor() {
		System.out.println("R = " + this.r + " : G = " + this.g + " : B = " + this.b);
	}
}

class Test {
	public static void main(String args[]) {
		Color color = new Color();
		
		color.setColor(0.2F , 0.3F , 1.0F);
		color.showColor();
	}
}
実行結果
>java Test
R = 51 : G = 76 : B = 255

コード6は、0.0 ~ 1.0 までの色の強さをパーセントで表す float 型から RGB 値を生成する SetColor() メソッドを宣言しています。このメソッドの仮パラメータには r、g、b という変数が宣言されており、これはフィールドの名前と衝突してしまいます。この場合、SetColor() メソッドのブロックで変数名だけを指定すると、ローカル変数が優先されます。そこで、this キーワードを記述しオブジェクトを明示的に表現することでフィールドにアクセスしているのです。ShowColor() メソッドでも this キーワードを使って明示的にメソッドを起動したオブジェクトを表していますが、これは省略可能です。 このような、識別子が衝突する宣言によって他の宣言の識別子がスコープ内で隠されてしまう宣言をシャドーイング宣言と呼びます。シャドーイング宣言によって隠されてしまった識別子にアクセスするには、限定名を使う必要があります。