プログラミング入門(9)


前回は 「プログラムの中で他のクラスのメソッドを呼び出す」 方法について学習しました。 具体的には、System クラスやMathクラスのメソッドを呼び出す プログラムを書きました。

また、クラスの使い方を調べるために "Java2 Platform, Standard Edition 1.4.0 API仕様" http://nw.tsuda.ac.jp/doc/j2sdk1.4.0/ja/api/index.html に WEB ブラウザでアクセスして、System クラスと Math クラスの 説明を読みました。 クラスに「フィールド」と「メソッド」が含まれることがわかったはずです。

たとえば Math クラスであれば以下の抜粋のように 「フィールド」と「メソッド」に関する説明がありました。

MathクラスのAPI仕様から抜粋
フィールドの概要
static doubleE   自然対数の底 e にもっとも近いdouble値です
static doublePI   円周とその直径の比 pi に最も近い double 値です
メソッドの概要
static doubleabs(double a)   double値の絶対値を返します。
static floatabs(float a)   float値の絶対値を返します。
static intabs(int a)   int値の絶対値を返します。
static longabs(long a)   long値の絶対値を返します。

この「フィールド」や「メソッド」とは何なのでしょうか? また、そもそも「クラス」とは一体何なのでしょうか? Javaの本を読むと、「Javaはオブジェクト指向言語である」と書かれてます。 「オブジェクト」とは一体どういうものなのでしょうか?


オブジェクト

「一連の動作を行なう部分をひとまとめにして他の場所から 呼び出せるようにしたもの」をJavaでは「メソッド」と呼びます。 他のプログラミング言語では異なる呼び方をして、 たとえばC言語では「関数」と、Pascal言語では「手続き」や「関数」 などと呼びます。

従来のプログラミング言語では、この「メソッド」を主体にして プログラムが作られていました。 まずメソッドがあって、それにデータを与えて計算を進めて行く イメージです。

図:オブジェクト指向でないプログラム

これではメソッドとデータをバラバラに扱うことなり、たとえば 間違ったデータを与えて間違ったメソッドを呼び出してしまう 「バグ」(= プログラム上の間違い)が 発生しやすくなります。また、将来データの種類が増えたときは 関係するメソッドを全部作り直す必要が発生します。

一方、オブジェクト指向言語では、データとメソッドをまとめた 「オブジェクト」を主体としてプログラムを作って行きます。 データの操作はそのオブジェクトに付属したメソッドを通して行ないます。

図:オブジェクト指向であるプログラム

このようなオブジェクトを使った方法では

など、プログラミング上の利点がいくつもあります。

Java言語は「オブジェクト指向言語」です。 次に「オブジェクト」と「クラス」について説明します。


クラスとオブジェクト

「オブジェクト」は「『データ』と『データの操作方法』をまとめたもの」です。 オブジェクトのことを「インスタンス」とも言います。

「クラス」は「オブジェクトの設計図である」と言うことができます。 すなわち、クラスには

の定義がまとめて書いてあります。 『フィールド』と『メソッド』はクラスの『メンバー』 であるということができます。 クラスには名前がつけられています。

「フィールド」には、「インスタンスフィールド」と「クラスフィールド」が あります。 クラスフィールドはstaticとして宣言されていて、 クラスに共通のデータを保持します。 クラスフィールドにはクラス名から直接アクセスできます (「クラス名 . クラスフィールド」という形式でアクセスできます)。 インスタンスフィールドはstatic宣言がされていないもので、 インスタンス毎に別の値を保持できます。

「メソッド」にも、「インスタンスメソッド」と「クラスメソッド」があります。 クラスメソッドはstaticとして宣言されていて、クラスで共通のメソッドです。 クラスメソッドからは、個々のインスタンスに属するインスタンスフィールド の操作はできません。 クラスメソッドはクラス名から直接アクセスできます (「クラス名 . クラスメソッド(パラメータリスト)」という形式で アクセスできます)。

クラスの概念を図にすると以下のように書けます。


クラスの定義例: Circle.java

「インスタンスフィールド」、「クラスフィールド」、 「インスタンスメソッド」、「クラスメソッド」の4つを含む クラスの例として 次に Circle クラスの例を示します。

クラスからインスタンスをつくり出す際に、インスタンス フィールドを初期化する必要があります。 そのために『コンストラクタ』と呼ばれるクラス名と同じ メソッドを記述します。コンストラクタは後に続く()の間に 呼び出すときのパラメータを記述します。

Circle.java (Circleクラスの定義例)
public class Circle {
    // クラスフィールド
    public static double PI = 3.14159; // 便利な定数
    // クラスメソッド: 引数に基づいて値を計算する
    public static double radiansToDegrees(double rads) {
	return rads * 180 / PI;
    }

    // インスタンスフィールド
    public double r;
    // インスタンスメソッド: インスタンスフィールドにアクセスする
    public double area() {	// 面積を求める
	double x;		// ローカル変数
	x = PI * r * r;
	return x;		// xの値を返す
    }
    public double circumference() { // 円周の長さを求める
	return 2 * PI * r;	// return の後ろに「式」を記述し、計算結果を返す
    }

    // コンストラクタ
    public Circle(double d) {  // 新しいインスタンスを生成するときに呼び出される
	r=d;
    }
}

RunCircle.java (Circleクラスを使うクラスの定義例)
public class RunCircle {
    public static void main(String args[]) {
	double x,y;
	System.out.println("πの値は" + Circle.PI + "です"); // クラスフィールドにアクセス
	x = 1.57;
	y = Circle.radiansToDegrees(x);	// クラスメソッドを呼び出す
	System.out.println("ラジアン" + x + "は" + y + "度です");

	Circle p = new Circle(5.0); // Circleクラスの新しいインスタンスを生成する
	double radius = p.r; // インスタンスフィールドへのアクセス
	double a = p.area(); // インスタンスメソッドを呼び出す
	System.out.println("半径" + radius + "の円の面積は " + a + "です");
    }
}
RunCircle.javaの実行例
sp204: ~/pro 4> <FONT COLOR=red><I>javac RunCircle.java</I></FONT> <IMG SRC="/local-icons/enter.gif">
sp204: ~/pro 5> <FONT COLOR=red><I>java RunCircle</I></FONT> <IMG SRC="/local-icons/enter.gif">
πの値は3.14159です
ラジアン1.57は89.95444981681251度です
半径5の円の面積は 78.53975です
sp204: ~/pro 6> <IMG SRC="/local-icons/prompt.gif">
  1. クラスフィールドへのアクセス

    publicと宣言されていますので、クラス外から クラスフィールドの PI にアクセスできます。 アクセスするにはCircle.PI のように、クラス名に . (ドット)をつけて フィールド名を書きます。

  2. クラスメソッドの呼び出し

    publicと宣言されていますので、クラス外からクラスメソッドを 呼び出すことができます。 呼び出すにはCircle.radiansToDegrees(x) のように、 クラス名に . (ドット)をつけてメソッド名を書き、その後に 括弧でくくってパラメータを書きます。

  3. インスタンス(=オブジェクト)の生成

    Circle クラスを新しいデータ型として定義したので、 Circle オブジェクトを保持する変数を次のように宣言できます。

        Circle p;
    

    Circleオブジェクトを保持する変数を宣言しただけでは、 Circleオブジェクトは生成されません。 オブジェクトを生成するには new 演算子を使います。 new演算子の後にはオブジェクト名と()が続きます。 ()の中にはオブジェクトを生成するためのコンストラクタメソッドに 渡す情報を記述します。 コンストラクタは新しいオブジェクトを生成し、その内部状態を 初期化します。

        Circle p = new Circle(5.0); // Circleクラスの新しいインスタンスを生成する
    
  4. インスタンス(オブジェクト)の使い方

    クラスはフィールドとメソッドの集合体を定義したものでした。 クラスとはいわばオブジェクトの設計図と言えます。 クラスのコンストラクタを new 演算子とともに呼び出すことによって、 そのクラスに属するオブジェクトを新しく生成します。 これは設計図に基づいて製品をつくり出すことに相当します。 オブジェクトは、クラスで定義されたインスタンスフィールドの コピーをそれぞれ持っていて、アクセスできます。 また、インスタンスメソッドを呼び出すこともできます。 オブジェクトのフィールドやメソッドにアクセスするには、 変数名の後にドット . を置いてフィールド名やメソッド名を 書きます。 メソッドを呼び出すときにはメソッド名の後に()を書いて、 ()の中にパラメータリストを書きます。

        Circle p = new Circle(5.0); // Circleクラスの新しいインスタンスを生成する
        double radius = p.r; // インスタンスフィールドへのアクセス
        doubble a = p.area(); // インスタンスメソッドを呼び出す
    
  5. クラス内のフィールドやメソッドへのアクセス

    同じクラスに属するフィールドは直接フィールド名を書くとアクセスできます。 また、同じクラスに属するメソッドは直接メソッド名を書いて指定することが できます。


メソッド

私達が Sample.java というJava言語のプログラムを書いたとき、 その中で

public class Sample {
という記述をしていたのは、実は Sample という 新しいクラスを定義していたのです。 さらに、その中に
    public static void main(String args[]) {
と記述していたのは、実は Sample クラスに属する main というメソッドを定義していたのでした。

メソッドを定義するには以下のように記述します。

修飾子  型   名前  (パラメータリスト)   { メソッド本体 }

return文

return文は、「現在実行しているメソッドを直ちに終了させる」 働きがあります。

メソッドが値を返す場合は、メソッド中に次のような return文を記述します。

    return 式;
この文を実行したとき、ただちにこのメソッドは実行を終了し、 メソッドの返り値として「式」を計算した結果が返されます。

メソッドが値を返さない場合は、メソッド中に次のような return文を記述します。

    return;
この文を実行したとき、ただちにそのメソッドは実行を終了しますが、 何も値は返されません。 メソッド本体の最後の文を実行し終ったときは、 メソッドの実行を終了し、メソッドを呼び出した式に戻ります。 これはすなわち、メソッド本体の最後に値を返さない
    return;
というreturn文が暗黙的におかれていることになります。

メソッドの呼び出し

メソッドを呼び出すときは、メソッド名の後に()をつけ、 その括弧の中にパラメータリストを書きます。 パラメータとしては「式」を指定できます。 メソッドに渡される情報は、式の計算結果の値が コピーされたものです。

すなわち、メソッドを呼び出すときに、実引数として (int, double, booleanなどの)基本データ型のデータを渡すと、 それはコピーして渡されますので、メソッドの中で変更しても 元のデータは変更されません。

もし int add1(int) というメソッドがあったとしてその定義が 次のようであるとします。

    int add1(int x) {
        x = x + 1;
        return x;
    }
このときに、もしメソッドを呼び出す側で以下のように書くと
    int  a, b;
    a = 2;
    b = add(a);
    System.out.println("a="+a+",b="+b);
画面には a=2, b=3と表示されます。変数aの値は 変更されないのです。

Java言語の文法では「メソッドの呼び出し」自体も「式」となります。 「メソッドの呼び出し」という「式」の値は、メソッドが返す「返り値」です。

上では、単に「パラメータ」(または引数)と呼んでいましたが、 特にメソッドの定義のときに書くパラメータ(引数)を 「仮引数」(かりひきすう)といいます。 またメソッドを呼び出すときに実際に渡すデータを 「実引数」(じつひきすう)と呼びます。

変数の通用範囲

開き中括弧 { と 閉じ中括弧 } で囲まれた部分をブロックと呼びます。 『メソッドにおける本体の定義』や、『複文』は { と }を使って、 すなわち、ブロックで表されています。

「変数」はブロックの中で宣言することができます。 Javaは変数の宣言に関して比較的柔軟でブロックのどこにでも 書くことができますが、変数の有効範囲はそのブロックの中だけです。 変数が有効である範囲のことを「通用範囲」(または、scope、スコープ) といいます。

メソッド定義のときに、パラメータ(仮引数)として宣言された変数や、 メソッドの本体の中で定義された変数は、そのメソッドの中だけで有効です。 他のメソッドの中に同じ名前の変数があっても全く関係がない (=全く別の変数として取り扱われる)ことに注意が必要です。


クラスを定義してみましょう

「体重と身長から BMI を計算する」というプログラムを以前作成 しました。 これをクラスを使って書き直してみます。

Profile1.java
public class Profile1 {
    double height;		// cm単位  インスタンスフィールド
    double weight;		// kg単位  インスタンスフィールド
    int age;			// インスタンスフィールド
    static double standardBMI = 22; // 標準BMI
    public Profile1(double h,double w,int a) { // コンストラクタ
	height = h;
	weight = w;
	age = a;
    }
    public void show() {	// インスタンスメソッド
	System.out.println("身長は " + height + " cmです。");
	System.out.println("体重は " + weight + " kgです。");
	System.out.println("年齢は " + age + " 歳です。");
    }
    public double getBMI() {	// インスタンスメソッド
	double h, bmi;
	h = height / 100;	// cm→m単位へ変換
	bmi = weight / (h * h);	// BMI値を計算する
	return bmi;		// BMI値を返す
    }
    public double getStandardWeight() {	// インスタンスメソッド
	double h, sw;
	h = height / 100;	// cm → m単位へ変換
	sw = h * h * 22;	// 標準体重を計算する
	return sw;		// 計算結果を返す
    }
}
RunProfile1.java (Profile1クラスを使うクラスの定義例)
import java.util.*;
public class RunProfile1 {
    public static void main(String args[]) {
	Scanner sc = new Scanner(System.in);
	double h,w;		// 入力した身長と体重
	int a;			// 入力した年齢
	Profile1 p;		// インスタンス
	double b, s;		// bmi値、と標準体重
	System.out.print("身長をcmで入力して下さい   ");
	h = sc.nextDouble();
	System.out.print("体重をkgで入力して下さい   ");
	w = sc.nextDouble();
	System.out.print("年齢を歳で入力して下さい   ");
	a = sc.nextInt();
	p = new Profile1(h,w,a);
	b = p.getBMI();
	s = p.getStandardWeight();
	p.show();
	System.out.println("BMI値は" + b + "です");
	System.out.println("あなたの身長の標準体重は" + s + "です");
    }
}
RunProfile1.javaの実行例
sp204: ~/pro 4> <FONT COLOR=red><I>javac RunProfile1.java</I></FONT> <IMG SRC="/local-icons/enter.gif">
sp204: ~/pro 5> <FONT COLOR=red><I>java RunProfile1</I></FONT> <IMG SRC="/local-icons/enter.gif">
身長をcmで入力して下さい   <FONT COLOR=red><I>175</I></FONT> <IMG SRC="/local-icons/enter.gif">
体重をkgで入力して下さい   <FONT COLOR=red><I>68</I></FONT> <IMG SRC="/local-icons/enter.gif">
年齢を歳で入力して下さい   <FONT COLOR=red><I>18</I></FONT> <IMG SRC="/local-icons/enter.gif">
身長は 175.0 cmです。
体重は 68.0 kgです。
年齢は 18 歳です。
BMI値は22.20408163265306です
あなたの身長の標準体重は67.375です
sp204: ~/pro 6> <IMG SRC="/local-icons/prompt.gif">


MyGraphicsクラス


まず MyGraphics.java をダウンロードして下さい(マウスの右ボタンを使って「対象を ファイルに保存」を選ぶこと)。 javaのプログラムを作るフォルダに保存して下さい。

GSample07.java が正しく動作するように、MyGraphics.java に public void drawRectangle(int sx,int sy,int w,int h,int r,int g,int b) というインスタンスメソッドの定義を追加して下さい。 ただし、これは座標(sx,sy)の点を左上の点とし、幅 w、高さ h の 四角形を描くメソッドです。

正しく動作することを確認したら、変更後の MyGraphics.java を 提出して下さい。

MyGraphics.java (一部省略)
public class MyGraphics {
    ToyGraphics tg;		// インスタンスフィールド
    public MyGraphics() {		// コンストラクタ
	tg = new ToyGraphics();	// 初期化
    }
    public void drawLine(int sx,int sy,int ex,int ey,int r,int g,int b) { // インスタンスメソッド
	int x, y;
	x=sx; y=sy;
	if (sx == ex) {		// 垂直の線
	    if (sy == ey) {
		tg.drawRGB(sx,sy,r,g,b);
	    } else if (sy < ey) {
		for (y=sy; y <= ey; y++) {
		    tg.drawRGB(x,y,r,g,b);
		}
	    } else {
		for (y=sy; y >= ey; y--) {
		    tg.drawRGB(x,y,r,g,b);
		}
	    }
	} else if (sy == ey) {	// 水平の線
	    if (sx < ex) {
		for (x=sx; x <= ex; x++) {
		    tg.drawRGB(x,y,r,g,b);
		}
	    } else {
		for (x=sx; x >= ex; x--) {
		    tg.drawRGB(x,y,r,g,b);
		}
	    }
	} else if (Math.abs(ex-sx) >= Math.abs(ey-sy)) { // xを動かす
	    if (sx < ex) {
		for (x=sx; x <= ex; x++) {
		    y = (ey - sy) * (x - sx) / (ex - sx) + sy;
		    tg.drawRGB(x,y,r,g,b);
		}
	    } else {
		for (x = sx; x >= ex; x--) {
		    y = (ey - sy) * (x - sx) / (ex - sx) + sy;
		    tg.drawRGB(x,y,r,g,b);
		}
	    }
	} else {		// yを動かす
	    if (sy <= ey) {
		for (y=sy; y<=ey; y++) {
		    x = (ex - sx) * (y - sy) / (ey - sy) + sx;
		    tg.drawRGB(x,y,r,g,b);
		}
	    } else {
		for (y=sy; y>= ey; y--) {
		    x = (ex - sx) * (y - sy) / (ey - sy) + sx;
		    tg.drawRGB(x,y,r,g,b);
		}
	    }
	}
    }
    public void drawRectangle(int sx,int sy,int w,int h,int r,int g,int b) {
	//  インスタンスメソッドを自分で書いて下さい:提出課題
    }
    //  インスタンスメソッドを自分で追加して下さい:提出課題
}
GSample07.java
public class GSample07 {
    public static void main(String args[]) {
	MyGraphics mg = new MyGraphics();
	mg.drawLine(100,100,550,120,255,0,0);
	mg.drawLine(100,100,150,450,0,255,0);
	mg.drawRectangle(200,200,100,100,0,255,255);
    }
}
GSample07.javaの実行例
sp204: ~/pro 4> <FONT COLOR=red><I>javac GSample07.java</I></FONT> <IMG SRC="/local-icons/enter.gif">
sp204: ~/pro 5> <FONT COLOR=red><I>java GSample07</I></FONT> <IMG SRC="/local-icons/enter.gif">




演習

提出課題4

MyGrahpics.java にいろいろなインスタンスメソッド (たとえば3角形などを描くメソッドなど)を追加して下さい。 MyGraphics.java に追加したいろいろなメソッドを呼び出して美しい 絵を描く GSample08.java を作って下さい。

「1年ゼミ提出課題3」に MyGraphics.java を、 「1年ゼミ提出課題4」に GSample08.java を提出して下さい。