Java クラス、メソッド

目次


メソッド

数学関数のような関数を自分で定義することができる。C++で関数と呼んだものを Java ではメソッドという。0個以上引数を受け取り、まとまった仕事を実行して0個また1個の(数値とは限らない)値を返す。メソッドの一般的な形式は次の通り。

(修飾子) 戻り値の型 メソッド名(引数の並び) { メソッドの定義プログラム }

「修飾子」は public, static などの属性を書く。 「戻り値の型」は、return に続けて値が置かれる場合はその値の型を書き、それがない場合は void と書く。引数は、データ型と変数名をスペースで区切ったペアとし、複数ペアある場合はカンマで区切る。データ型は変数ごとに指定しければいけない。メソッドの定義プログラムは、1個以上の命令文、あるいは宣言文、をセミコロン(と改行)で区切って並べる。複数の命令文を { } でまとめたコードブロックの場合は、 } の後のセミコロンは不要。

public static 属性を持つ main() メソッドはコンパイル後最初に実行されるメソッド、という特別な意味を持つ。

メソッドを実行させるためには、

メソッド名(引数の並び);

という命令文を実行すれば良い。このとき、引数の並びはメソッドの定義で指定された引数の並びと個数、型が一致したもので、(型は指定せず)その値だけを書く。2個以上ある時はカンマで区切る。ここで書かれた引数は、メソッドの定義で書かれた引数と区別するために実引数と呼ばれ、メソッドの定義で書かれた引数は仮引数と呼ばれる。仮引数は変数名でなければいけないが、実引数は変数名でも定数でも、はたまたメソッド実行文であっても、仮引数に対応する型の値が確定するものであれば、なんでも構わない。実引数が仮引数の型と合わない場合、調整される場合もあるが、合わせておいたほうが間違いがない。

配列データを実引数とする場合は配列名だけを書く。文字列データは1文字を要素とする配列とみなされるので、文字配列名だけを書く。配列の長さは、配列名に続けて「.length」と書くことで参照できるので、引数とする必要はない。

メソッドの中で使用される変数のうち、メソッド内で宣言された変数は、そのメソッドの中だけで有効で、メソッドを終了すると未定義状態になる。宣言なしに変数を使用する場合は、そのメソッドを含むクラスの変数名として定義されていなければいけない(C++ のグローバル変数に対応する)。

オーバーロード(多重定義)

同じメソッド名で引数の並び(だけ)が違うメソッドを同じクラス内で定義することができる。この定義の仕方はオーバーロードと呼ばれる。違う型のデータに適用したり、一部の引数を定数化したりするが作業内容は同じ(ようなもの)という場合に使用され、プログラムの可読性のために有効な手法である。

仮引数に int... a と書き、実引数に int 型数を n 個並べると、a[0], a[1], ... a[n-1] が実引数としてメソッドに渡されたことになる。この場合、n はメソッドを呼び出すたびに変わっても良い。これを可変変数という。可変変数は、通常の引数と共存できるが、必ず、最後の引数として指定しなければいけない。

再帰的メソッド

メソッドの中から自分自身を呼び出すことができる。このような参照の仕方は再帰呼び出しと言われる。

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

if 文は再帰の連鎖を打ち切るために必要で、それがないと暴走する。再帰呼び出しするとき、メソッドの処理は途中なので、その作業内容を確保したまま次の呼び出されたメソッドの処理を開始するので、再帰の連鎖が長くなると場合によってはパンクしてしまうこともありうる。あまり大きな処理プログラムに再帰呼び出しは使わないほうが良い。

クラス

Javaのプログラムの基本単位はクラスclassで、1個以上のクラスを集めたものをパッケージpackage としてまとめることができる。単純な課題であれば、一つのクラスで足りるが、そうでない場合は、まとまった仕事単位にクラスを定義し、それらをパッケージにまとめ、有機的に結びつけることで課題解決のプログラムを作るというプログラミングのスタイルは作業の効率化のために有効である。

クラスは0個以上のデータの宣言文とメソッドからなる。クラスは、変数名と同じ規則に従った名前(クラス名)を指定して次のように定義する。宣言文により変数には初期値が設定される。明示的に指定した場合はその値、それがない場合は、数値、char型は0、論理変数は false、それ以外は null となる。

class クラス名 {
0個以上の宣言文
0個以上のメソッド
}

あるいは

public class クラス名 {
...
}

public は他のパッケージから参照したい場合に付ける修飾子である。

Java のアプリケーションプログラムのパッケージには、(プログラムを起動するために)public static void main() というメソッドを含むクラスがちょうど1つだけ含まれていなければいけない。

継承、親クラス、子クラス

クラスは一から作る場合もあるが、すでに定義されている似た仕事をするクラスの一部を書き換えて新たなクラスとする場合が多い。

class クラス名1 extends クラス名2{
...
}

のように始まるクラスの定義は、「クラス名1」で定義されているメソッド以外は「クラス名2」で定義されているメソッドをそのまま取り込む、ということを意味している。両方のクラスで同じ名前のメソッドがある場合は「クラス名1」のメソッドが優先される。このとき、「クラス名2」のメソッドは「クラス名1」のメソッドにオーバーライドされたという。

「クラス名1」を親クラス(スーパークラス)、「クラス名1」を子クラス(サブクラス)と言い、子クラスは親クラスを継承したという。Javaには、その目的のために汎用のクラスが多数定義されており、プログラミング作業の軽減に役立っている。(後述のインターフェースを除き)二つ以上の親クラスを継承することはできない。

例えば、次は、Java が定義している Canvas クラスを継承した子クラスの例である。フィールドはなく、コンストラクタと呼ばれる MyCanvas メソッドと、paint メソッドが定義されている。paint メソッドは親クラスで定義されているメソッドでもあるが、子クラスでオーバーライドされている。

class MyCanvas extends Canvas {
	MyCanvas(Dimension size) {
		this.setSize(size);
	}
	public void paint(Graphics g) {
		g.drawRect(10, 10, this.getWidth()-20, this.getHeight()-20);
		g.drawString("MyCanvas", 20, 40);
	}
}

インスタンス

main() メソッドを含まないクラスは、変数とメソッドの定義集のようなもので、それ自体として実行することはできない。そのメソッドを実行するためには、他のクラス次のような宣言文を使って実体を与える必要がある。

クラス名 変数名 = new クラス名(引数リスト);

二つの「クラス名」は同じものである。基本データ型の変数と同じように、この変数も「クラス名」というデータ型を持つ。この変数は「クラス名」のインスタンス(名)(一例、実体、分身?)と呼ばれる。

「クラス名」で定義されているメソッド・変数は、インスタンス名の後に続けて「.」+メソッド名、あるいは「.」+変数名で呼び出すことにより利用することができる。

「クラス名」型の配列変数を定義することもできる。この場合は、配列であることを宣言した後、配列の各要素(それがインスタンスになる)に対して new を使って実体を与えなければいけない。

クラス名[] 変数名 = new クラス名[10];
for(i=0; i<10; i++) {
変数名[i] = new クラス名(引数リスト);
}

ただし、public static void という属性を持つ main() メソッドを持つクラスは特別な意味がある。main と呼ばれるメソッドを持つクラスと、アプレットと呼ばれる Applet クラスを継承したクラスは、インスタンスを作成することなく、それ独自で実行することが出来る。

次のプログラムは、アプレットの例で、先に定義した MyCanvas クラスのインスタンスを生成して実行させたものである。こうすることによって、MyCanvas クラスの MyCanvas, paint メソッドが実行され、ブラウザに長方形と文字列が表示されるようになる。

public class Test extends Applet {
	public void init() {
		add(new MyCanvas(this.getSize()));
	}
}
class MyCanvas extends Canvas {
	MyCanvas(Dimension size) {
		this.setSize(size);
	}
	public void paint(Graphics g) {
		g.drawRect(10, 10, this.getWidth()-20, this.getHeight()-20);
		g.drawString("MyCanvas", 20, 40);
	}
}

次のプログラムは、他で定義されているクラスの変数を、インスタンスを作らずにクラス名を直接使って呼び出して利用する例である。この場合、変数は必ず static 属性の指定が必要である。

class Test {
	public static void main(String[] args) {
		int n = (int)(Math.random() * 7);
		System.out.println("今日は " + Days.DOTW[n] + "曜日です");
	}
}
class Days {
	public static String[] DOTW = {"日","月","火","水","木","金","土"};
}

クラスのインスタンスはオブジェクト(実体)と呼ばれ、オブジェクトの機能を組み合わせてプログラムができあがるところから、Java はオブジェクト指向言語と呼ばれている。

上の MyApplet クラスにある先頭の public は、アクセス修飾子と呼ばれ、他のパッケージからもアクセス可能であることを示す。こうすることで、実行時に最初に実行されるクラスであることを指定している。Days クラスの DOTW フィールドの先頭にある public アクセス修飾子は、パッケージ内の他のクラスからアクセス出来ることを指定している。

コンストラクタ

クラスのメソッドとして、コンストラクタと呼ばれるクラス名と同じ名前を持つ特別なメソッドがある。クラスのインスタンスが作られるときにそのメソッドが呼び出されて実行される。いわば、初期化作業を集めたもの、と言ってよい。コンストラクタはメソッドなので、引数リストを指定できる。インスタンスを作るときは、その引数リストに対応する実引数を指定しなければいけない。戻り値を設定できないので、メソッド名の前に「戻り値の型」は書かない。

初期設定する変数の仮変数として、同じ変数名を使用することがある。その場合、変数に値を設定する代入文の左辺と右辺が同じ名前になって混乱する。そのわかりにくさを避けるために(このクラスの、という意味で)this キーワードがある。変数名を x としたとき、this.x = x; と書くと、仮変数の x の値をこのクラスの変数 x に代入する、という命令になる。

引数リストの異なる、複数のコンストラクタを定義することができる。これを(メソッドの場合と同じように)コンストラクタのオーバーロードという。

先に定義した MyCanvas では、キャンバスの大きさを決める setSize という、親クラスで定義されているメソッドを実行して、引数で渡された大きさを設定している。

カプセル化

変数の宣言文、あるいはメソッドの修飾子として private を指定すると、その変数、あるいはメソッドは、そのクラスの内部だけでしか参照できなくなる。変数を private 指定することで、その変数内容を勝手に変更できないようにすることができ、データを保護することができる。(そのクラス内部でだけ意味のある)メソッドを private 指定することで、クラスのアクセス環境を単純化することになり、使い勝手が向上することが期待できる。このような考え方はカプセル化と呼ばれる。

データ型定義

メソッドのないクラスは、レコードとも呼ばれ、新しいデータ型を定義するために使われる。たとえば、名前、住所、職業などの個人データを一つのクラスにまとめ、一つの名前で参照できるような場合である。

class PersonalRecord {
String name, address, tel, occupation;
}

使う場合は:
class ... {
PersonalRecord taro = new PersonalRecord();
...
System.out.println(taro.name + " の電話番号は " + taro.tel);
}

メソッドがあっても、コンストラクタだけ、というクラスもある。例えば、次は3次元の点というデータ型を定義したクラスの例である。

class Point3D {
int x, y, z;
Point3D(int x, int y, int z) {
this.x = x;
this.y = y;
this.z = z;
}
}

使う場合は:
class ... {
Point3D origin = new Point3D(0,0,0);
...
}

インターフェース

Java は共同作業によって大きなプログラムを作ることを想定しており、その作業環境を整えることを考えてクラスと継承という仕組みを作った。その目的で、多くの汎用クラスがシステムによって提供されているので、それらを利用することで、プログラムの枠組みが標準化され、作業が効率的に行われるように工夫されている。さらに、継承により、すべての作業を1から始めることなく、必要なパーツを組み合わせることで素早く目標を達成することが出来るようになっている。

しかし、継承という便利な仕組みには複数の親クラスを継承することが出来ない、という大きな制約がある。この制約をなくすための工夫がインターフェースである。インターフェースはクラスの一種で、変数の宣言文とメソッドからなるが、次のような制約がある。

3番目と4番目の制約から、インターフェースを共有すれば、同じ名前を使って作業する環境が整うことになる。親クラスと異なり、インター フェースは複数を同時に実装することが出来るので、作業目的毎に作成しておけば管理しやすくなる。次のプログラムは、前に出したクラスのフィールドを直接 参照する例を、インターフェースを使って書き直したものである。変数の属性として必要だった public static が不要になっている。

class Test implements Days {
	public static void main(String[] args) {
		int n = (int)(Math.random() * 7);
		System.out.println("今日は " + Days.DOTW[n] + "曜日です");
	}
}
interface Days {
	String[] DOTW = {"日","月","火","水","木","金","土"};
}

クラスと同様、様々な汎用インターフェースがシステムによって用意されている。次は、ボタンクリックの処理プログラムとして ActionListener と呼ばれるインターフェースを実装して、actionPerformed メソッドをオーバーライドした例である。ボタンをクリックすると actionPerformed メソッドが実行され、ボタンを識別すると、架空の曜日を生成して表示する。

public class Test extends Applet implements ActionListener, Days{
	JLabel lb = new JLabel("...");
	public void init() {
		JButton bt =  new JButton("架空の曜日");
		bt.addActionListener(this);
		add(bt);
		add(lb);
	}
	public void actionPerformed(ActionEvent e) {
		if(e.getActionCommand() == "架空の曜日") {
			int n = (int)(Math.random() * 7);
			lb.setText("今日は " + Days.DOTW[n] + "曜日です");		
		}
	}
}
interface Days {
	String[] DOTW = {"日","月","火","水","木","金","土"};
}