情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引

第Ⅲ部

第7章 クラスとメソッド

  1. 7.1 メソッド
    1. (プログラム例 7.1 ) 階乗の計算
    2. (プログラム例 7.2 ) 階乗の計算(関数の利用)
    3. (プログラム例 7.3 ) 階乗の計算(再帰呼び出し)
    4. (プログラム例 7.4 ) nrの計算
  2. 7.2 クラス
    1. (プログラム例 7.5 ) クラス宣言とメンバーの参照( C/C++ との違い)
    2. (プログラム例 7.6 ) private 変数
    3. (プログラム例 7.7 ) 静的変数・メソッドとインスタンス変数・メソッド
  3. 7.3 コンストラクタ
    1. (プログラム例 7.8 ) コンストラクタ
  4. 7.4 メソッドとのデータの受け渡し
    1. (プログラム例 7.9 ) 様々なデータ型の引き渡し
    2. (プログラム例 7.10 ) 複数結果の受け取り
    3. (プログラム例 7.11 ) メソッド名の引き渡し
  5. 7.5 main メソッド
    1. (プログラム例 7.12 ) main メソッドの引数(数字の和)
  6. 7.6 パッケージ(Java)
    1. (プログラム例 7.12 ) パッケージ
  7. 7.7 いくつかの例
    1. (プログラム例 7.14 ) プラントモデル
    2. (プログラム例 7.15 ) ベクトルの内積と大きさ
    3. (プログラム例 7.16 ) リスト構造
    4. (プログラム例 7.17 ) ソート(並べ替え)
    5. (プログラム例 7.18 ) カレンダー
    6. (プログラム例 7.19 ) 基本アルゴリズム(その1)
    7. (プログラム例 7.20 ) 基本アルゴリズム(その2)
  8. 演習問題7

7.1 メソッド

  メソッド( C/C++ においては関数,function と呼ぶ,method)とは,外部から情報を与えられ,その情報に従って何らかの処理を行い,結果を返すという作業を行う手続きの集まりです.メソッドによっては,情報を必要としないものや結果を返さないものも存在します.今までのプログラムで使用してきた main もメソッドの一種ですが,その名前(メソッド名)が main と決まっており,一つのプログラム内に 1 個だけ存在しなければならないという特別なメソッドです.メソッドの一般的な定義方法は以下の通りです.
[メソッド修飾子] メソッドの型 メソッド名 ( 引数リスト )		
ここで,引数とは,メソッドを呼び出した側とメソッドとの間で,情報の受け渡しに使用される変数や定数のことです.main メソッドの場合は,String args[] が引数に相当します.また,メソッドは計算した結果を何らかの値として戻しますが,メソッドの型とはその値の型のことです.main メソッドの場合は,void がそれに対応しますが,これは,結果を返さないことを意味しています.詳細については,以下の例を参照して下さい.また,main メソッドの public static がメソッド修飾子に相当しますが,メソッド修飾子については,7.2 節において説明します.

  プログラムの複数の場所で同じことを繰り返す場合や,プログラムが長くなりすぎる場合は,メソッドを積極的に利用することによって,わかりやすいプログラムを書くことが可能です.まず,この節では,簡単な例によって,メソッドの基本的な概念について説明します.

(プログラム例 7.1 ) 階乗の計算 

  この例は,入力されたデータ n (整数)の階乗を計算するためのプログラムです.今までのプログラムと同じように,main メソッドだけを使用して書いています.階乗の値は,通常,非常に大きくなるため,double 型で計算しています.
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	/*
		 データの入力
	*/
		System.out.print("nの値を入力して下さい ");
		int n = Integer.parseInt(in.readLine());
	/*
		 階乗の計算
	*/
		double kai = 1.0;               // 初期設定
		for (int i1 = 1; i1 <= n; i1++)
			kai *= (double)i1;
	/*
		 結果の出力
	*/
		System.out.println("   " + n + "の階乗は=" + kai);
	}
}
		

(プログラム例 7.2 ) 階乗の計算(メソッドの利用) 

  このプログラムでは,プログラム例 7.1 と同じ内容の計算を,main メソッドから,階乗を計算するためのメソッド kaijo を呼び出して行っています.以下の説明により,メソッドの基本的事項を理解して下さい.

01	/****************************/
02	/* nの階乗の計算           */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	public class Test {
08		public static void main(String args[]) throws IOException
09		{
10			BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
11		/*
12			 データの入力
13		*/
14			System.out.print("nの値を入力して下さい ");
15			int n = Integer.parseInt(in.readLine());
16		/*
17			 階乗の計算
18		*/
19			double kai = kaijo(n);   // kai = (new Test()).kaijo(n); のようにすれば,
20	                                 // メソッドkaijoに対するstatic宣言は不要
21		/*
22			 結果の出力
23		*/
24			System.out.println("   " + n + "の階乗は=" + kai);
25		}
26	
27		/**************************/
28		/* mの階乗               */
29		/*      m : データ        */
30		/*      return : nの階乗 */
31		/**************************/
32		static double kaijo(int m)
33		{
34			double s = 1.0;
35			for (int i1 = 1; i1 <= m; i1++)
36				s *= (double)i1;
37			return s;
38		}
39	}
		
19 行目

  メソッド kaijo に引数として n を設定してメソッドを呼び出し,その結果を変数 kai に代入しています.この行は,24 行目と一緒にして,次のようにも書けます.
	System.out.println("   " + n + "の階乗は=" + kaijo(n));			
27 ~ 31 行目

  32 から 38 行目までが,メソッド kaijo の本体(メソッドの定義)です.これらの行は,メソッドの機能についての注釈です.このように,少なくとも,メソッドの機能,引数の意味,返す値に対する説明は必ず書いておいて下さい.

32 行目

  各メソッド定義の最初に記述する文です.メソッド kaijo に対して,メソッド修飾子として static が宣言されていますが,main メソッドが static 宣言されている関係上,static 宣言しておかないと「kai = kaijo(n)」のような呼び出しが不可能になるからです.メソッドの型は double です.メソッド kaijo が double 型の値を返すことを宣言しています.なお,main メソッドのように,値を返さない場合は void と宣言します,

  括弧内の int m は,int 型の値を 1 つ引数として受け取ることを意味しています.この例のように,引数の名前は呼び出し時と異なっても構いません(もちろん,同じでも構いません)が,変数の型や括弧内に記述する順番は,19 行目と 32 行目で必ず一致しなければなりません.この結果,main メソッドにおける n の値がメソッド kaijo の m にコピーされてメソッドが実行されます.基本的に,各メソッドは,全く別のプログラムであると考えた方が理解しやすいと思います.従って,メソッドを呼び出した側の情報を,呼び出されたメソッドに伝えるには何らかの処理が必要です.例えば,階乗を計算する場合,階乗の対象となる n 自身の値が不明であれば,計算不可能です.情報を伝える 1 つの方法が引数です.もちろん,引き渡す情報を必要としなければ,引数も必要ありませんし,また,複数の引数を渡すことも可能です.

  しかし,その値のコピーが渡されるため,たとえメソッド kaijo の引数として main メソッドと同じ名前の変数 n を使用していても,main メソッドの変数 n と kaijo メソッドの変数 n とは異なる変数です.従って,もし kaijo メソッド内で変数 n の値を変更しても,main メソッド内の変数 n の値は変化しません.メソッド内で定義されている他の変数( 34 行目の s など,ローカル変数と呼びます)についても同様です.あるメソッドと別のメソッドにおいて,同じ名前の変数が使用されていても,それらは全く関係ありません.あるメソッドで,ある変数の値を変化させた場合,たとえそれが別のメソッドの変数と同じ名前であっても,その情報が何らかの方法で受け渡されない限り,別のメソッドではその影響を全く受けません.

37 行目

  メソッドでの処理を終了し,その結果を返すための文です.void 型のメソッドでない限り,必ずこの文が必要になります.なお,s の型はメソッドの型と一致している必要があります.

  メソッド kaijo を static 宣言したくない場合は,以下に示すように,別のクラスを定義し,その中で kaijo を呼ぶようにすれば可能です.
/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		Exam ex = new Exam();
	}
}

class Exam
{
	Exam() throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	/*
		 データの入力
	*/
		System.out.print("nの値を入力して下さい ");
		int n = Integer.parseInt(in.readLine());
	/*
		 階乗の計算
	*/
		double kai = kaijo(n);   // kai = (new Test()).kaijo(n); のようにすれば,
                                 // メソッドkaijoに対するstatic宣言は不要
	/*
		 結果の出力
	*/
		System.out.println("   " + n + "の階乗は=" + kai);
	}

	/**************************/
	/* mの階乗               */
	/*      m : データ        */
	/*      return : nの階乗 */
	/**************************/
	double kaijo(int m)
	{
		double s = 1.0;
		for (int i1 = 1; i1 <= m; i1++)
			s *= (double)i1;
		return s;
	}
}
		

(プログラム例 7.3 ) 階乗の計算(再帰呼び出し) 

  先の例では,main から kaijo というメソッドを呼び出しただけでしたが,メソッドの中で,例えば kaijo の中で別のメソッドを呼び出すことも可能です.特別の場合として,自分自身を呼び出すことも可能です.それを,再帰呼び出しrecursive call )といいます.再帰呼び出しを頻繁に使用すると,プログラムが分かり難くなる場合が多々あります.やむを得ない場合以外,再帰呼び出しの使用は避けた方がよいと思います.このプログラムは,その概念を理解してもらうために,階乗の計算に再帰呼び出しを利用しています.自分で,実行手順を追ってみて下さい.

/****************************/
/* nの階乗の計算           */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	/*
		 データの入力
	*/
		System.out.print("nの値を入力して下さい ");
		int n = Integer.parseInt(in.readLine());
	/*
		 階乗の計算
	*/
		double kai = kaijo(n);
	/*
		 結果の出力
	*/
		System.out.println("   " + n + "の階乗は=" + kai);
	}

	/**************************/
	/* mの階乗               */
	/*      m : データ        */
	/*      return : nの階乗 */
	/**************************/
	static double kaijo(int m)
	{
		double s;

		if (m > 1)
			s = m * kaijo(m-1);     // 自分自身を呼んでいる
		else
			s = 1;

		return s;
	}
}
		

(プログラム例 7.4 ) nrの計算 

  今までの例では,メソッドを使用する便利さがあまり明確でなかったかもしれません.そこで,この例では,上で作成した階乗を計算するメソッド kaijo を利用して,nr(= n! / (r! (n - r)!) )の計算をしてみます.nrの計算では,階乗の計算を多く行いますので,メソッド kaijo を利用すると,下に示すように,非常に簡単に書くことができます.

  また,この例のように,メソッドを呼び出すときの引数として式を書くこともできます.

/****************************/
/* nCrの計算               */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
	/*
		 データの入力
	*/
		System.out.print("nの値は? ");
		int n = Integer.parseInt(in.readLine());
		System.out.print("rの値は? ");
		int r = Integer.parseInt(in.readLine());
	/*
		 nCrの計算と出力
	*/
		double sn  = kaijo(n);
		double sr  = kaijo(r);
		double snr = kaijo(n-r);
		System.out.println("   " + n + "C" + r + "は=" + sn/(sr*snr));
	}

	/**************************/
	/* mの階乗               */
	/*      m : データ        */
	/*      return : nの階乗 */
	/**************************/
	static double kaijo(int m)
	{
		double s = 1.0;
		for (int i1 = 1; i1 <= m; i1++)
			s *= (double)i1;
		return s;
	}
}
		

7.2 クラス

  大きなプログラムの場合,その部分的な機能の修正のため,プログラム全体にわたって修正をしなければならないとしたら,大変なことになります.そこで,プログラムのモジュール化が非常に重要になります.各機能毎にモジュール化し,

  1. そのモジュール内部における詳細な処理を知らなくても,適当なインターフェースを介してデータを渡し,また,インターフェースを介して希望する結果が得られる.

  2. モジュールとのやりとりは,インターフェースを介してのみ可能であり,モジュールの外部から,そのモジュール内の処理を直接コントロールすることはできない.

ようにしておけば,対応する機能の修正はそのモジュールの修正だけで済みます.つまり,各モジュールをブラックボックス化するわけです.

  クラスとは,ある「もの」に対し,その「もの」に共通する特徴等をもとにして,形式的な定義を与えたものであるといえます.もう少しプログラミング的な感覚でいえば,「もの」に共通するデータと,それらのデータを処理する方法を記述した手続きの集まりです.例えば,「車」というクラスを定義したとすれば,車の構造を記述するデータと車を動かしたりするのに必要な手続きからなっているはずです.また,クラスは「もの」に対する抽象的な定義ですが,そのインスタンスオブジェクトとなる)はクラスを具体化したものに相当します.例えば,「車」クラスの場合であれば,そのインスタンスは,ある特定の人が所有する特定の車になります.

  クラスは,その定義の方法から見て,ユーザーが新たに定義する変数の型と考えても良いと思います.新しい変数の型を定義すれば,その型の操作方法も必要になります.例えば,複素数に対応するような型をクラスによって定義したとします.すると,単純な加算ですら,既存の方法を使用することができません.従って,定義した変数の加算をどのようにして実行するかについても定義してやる必要が出てきます.そこで,クラスの定義には,単にデータだけでなく,そのデータを取り扱う方法を記述したメソッドが必要になってくるわけです.

  Java では,すべてのメソッドが,main メソッドを含め,いずれかのクラスに所属しているため,C++ におけるフレンド関数のようなものは存在しません.さらに,メソッドの本体は,必ずクラスの定義内に記述する必要があります.この制限はかなり問題だと思います.特に,大きなクラスを定義した場合,そこに含まれるメソッドの数も非常に多くなります.それらをクラス定義内(同じファイル内)に記述しなければならないため,ファイルが非常に大きくなり取り扱いが面倒になります.もちろん,そのクラスを幾つかのクラスに分割すればある程度避けることができますが,分割せず一つのクラスとして扱う方が考え方として好ましいクラスも多く存在するはずです.

  クラスを定義する一般的な方法は以下の通りです. 

01	[クラス修飾子] class クラス名 {
02			// クラス本体(変数やメソッドの定義,定義の順番は任意)
03		[変数修飾子] データ型 変数名1,・・・;
04		[変数修飾子] データ型 変数名2,・・・;
05			・・・・・
06		[メソッド修飾子] メソッドの型 メソッド名1 (引数の並び) {
07			データ型 変数名1,・・・;
08			メソッドにおける処理
09		}
10		[メソッド修飾子] メソッドの型 メソッド名2 (引数の並び) {
11			データ型 変数名A,・・・;
12			メソッドにおける処理
13		}
14			・・・・・
15	}		

  今までに学習したプログラムは,クラス修飾子が public static であり,クラス名が Test というクラスだけから構成されていました(クラス名に関しては任意).また,クラス本体には,3 ~ 4 行目に対応するようにメソッドの外側で宣言する変数は存在しませんでした.一般に,メソッド内で定義された変数は,ローカル変数と呼ばれ,メソッドが実行されるときだけ有効になり,他のメソッドやクラスからそれらの変数を参照することは出来ません.異なるメソッド内で同じ名前の変数が使用されていても,それらは全く異なる変数とみなされます.しかし,3 ~ 4 行目のように,クラス内,かつ,メソッド外で定義された変数は,同じクラス内のメソッドから参照できると共に,その定義方法によっては,他のクラスからも参照できます.また,メソッドに関しても,他のクラスから参照可能なように定義することも可能です.これらのことを実現するのが,以下に述べるクラス修飾子,メソッド修飾子,及び,変数修飾子です.

  クラス修飾子は,以下のいずれかから選択します.クラス修飾子を使用せずにクラスを定義すると,同じパッケージ(後述)内にあるクラスだけがこのクラスにアクセスできるものと自動的に判断されます.

  1. public  すべてのクラスからアクセス可能になります.なお,一つのファイル内で public 宣言できるのは一つのクラスだけです.

  2. abstract  抽象クラスであることを示します.スーパークラスとしてだけ使用でき,クラスのインスタンスを生成できません.抽象クラスのメソッドには,定義だけで,その処理内容が含まれていない場合が多くあります.仕様の詳細を決めずに,抽象的な概念の定義だけを行います.

  3. final  拡張できない(継承できない)クラスであり,実装されているメソッドをオーバーライド(スーパークラスで定義されたメソッドを,書き換えて再定義すること)出来ません.例えば,数学的メソッドを多く含む Math クラスは final クラスです.なお,abstract 指定と final 指定を同時に行うことは出来ません.

  C++ のように関数や変数をまとめてそのアクセス権等を定義するようなことはせず,個々のメソッドや変数の定義に修飾子として直接記述します.メソッドを定義するためのメソッド修飾子は 8 つあり,以下の 3 つのグループの中から一つずつ選択して使用します(必ず,3 つを指定しなければならないわけではありません).ただし,修飾子の組み合わせによっては,矛盾が生じるような場合もありますので注意してください.

  1. public,protected,private (何も指定しないと,同じパッケージ及びクラスのオブジェクトだけからアクセス可能となる)

    • public  このメソッドをすべてのクラスからアクセスできます.異なるパッケージからアクセスしたい場合は,必ず指定する必要があります.
    • protected  同じパッケージ及びクラスのサブクラスだけからアクセスできます.
    • private  同じクラス内に定義されているメソッドだけからアクセスできます.

  2. static

    • static  通常のメソッドは,各オブジェクトに結びつき,そのオブジェクトからアクセス可能ですが,static 指定されたメソッドは,クラスに結びついているため,一般的には,「オブジェクト名.メソッド名」ではなく,「クラス名.メソッド名」という方法で参照します.static 指定されたメソッドを静的メソッド,それ以外のメソッドをインスタンスメソッドと呼びます.例えば,今まで幾つかの例で使用した Math クラスのメソッドは static 宣言されています.したがって,ある値 x の正弦を計算したいときには「 Math.sin(x) 」のような形で使用します.なお,静的メソッドから参照できる変数は,静的変数だけとなります.

  3. abstract,final,native,synchronized

    • abstract  抽象メソッドであることを意味しています.抽象メソッドを含むクラスは,クラス自身も abstract 宣言されている必要があります.
    • final  オーバーライド出来ないメソッドです..
    • native  他の言語で記述されたメソッドであることを意味します.
    • synchronized  オブジェクトが複数のオブジェクトから同時に実行されることを制限するために使用されます.マルチスレッドの実行の際,重要になります.

  変数修飾子は 7 つあります.以下の 2 つのグループから一つずつを選択して使用します.メソッドの修飾子と同様,必ず修飾子を付けなければならないわけではありません.なお,修飾子を付加できるのは,メソッド外で定義された変数だけです.

  1. public,protected,private (何も指定しないと,同じパッケージ及びクラスのオブジェクトだけからアクセス可能となる)

    • public  この変数をすべてのクラスからアクセスできます.異なるパッケージからアクセスしたい場合は,必ず指定する必要があります.
    • protected  同じパッケージ及びクラスのサブクラスだけからアクセスできます.
    • private  同じクラス内に定義されているメソッドだけからアクセスできます.

  2. static,final,transient,volatile

    • static  メソッドの場合と同様,クラスに結びついた変数です.したがって,オブジェクトごとに値を変化させるようなことはできません.アクセスも,「オブジェクト名.変数名」ではなく,「クラス名.変数名」というようにして行います.static 指定された変数を静的変数,それ以外の変数をインスタンス変数と呼びます.例えば,Math クラスの PI(π) などが相当します.
    • final  書き換えできない変数であり,定数として使用できます.
    • transient  クラスの永続的な状態の一部ではない変数.
    • volatile  非同期方式の多重処理で使用され,この変数は使われるたびにメモリからロードされ,また,メモリに格納されます.

  クラス Example のインスタンス(オブジェクト)を生成し,その中に定義されている変数(フィールド,メンバー変数)やメソッド(メンバー関数)を参照するには,基本的に以下のようにして行います.
EXample ex = new Example;   // ex は Example 型オブジェクトへのポインタと見て良い
ex.x = 20;   // クラス内に定義されている変数 x の参照
y    = ex.func(10);   // クラス内に定義されているメソッド func の参照		

(プログラム例 7.5 ) クラス宣言とメンバーの参照( C/C++ との違い)

  次に示す例は,クラス Complex の定義やその利用方法を示しています.なお,以下に示す例では,生成するオブジェクトの初期設定を行うための特別なメソッド-コンストラクタ-を利用しています.コンストラクタについては後述しますが,このプログラムにおける 15 行目の処理は,
c1 = new Complex();
c1.re = 1.0;
c1.im = 2.0;		
とほぼ同じです.

01	import java.io.*;
02	
03	class Complex {
04		public double re, im;
05		public Complex (double x, double y)   // コンストラクタ
06		{
07			re = x;
08			im = y;
09		}
10	}
11	
12	public class Test {
13		public static void main(String args[]) throws IOException
14		{
15			Complex c1 = new Complex(1.0, 2.0);   // コンストラクタにより,変数 re,im に 1,2 を設定
16			double x = 10.0;
17			System.out.printf("変更前 c1 = %f %f x = %f\n", c1.re, c1.im, x);
18			Complex c2 = c1;
19			double y = x;
20			System.out.printf("変更前 c2 = %f %f y = %f\n", c2.re, c2.im, y);
21			c2.re = 3.0;
22			c2.im = 4.0;
23			y = 20.0;
24			System.out.printf("変更後 c1 = %f %f x = %f\n", c1.re, c1.im, x);
25			System.out.printf("変更後 c2 = %f %f y = %f\n", c2.re, c2.im, y);
26		}
27	}
		

  このプログラムを実行すると,以下に示すような結果が得られます.
	変更前 c1 = 1.000000 2.000000 x = 10.000000
	変更前 c2 = 1.000000 2.000000 y = 10.000000
	変更後 c1 = 3.000000 4.000000 x = 10.000000
	変更後 c2 = 3.000000 4.000000 y = 20.000000			
  最初に,16 ,19 行目で定義された変数 x,y の変化を見て下さい.16 行目で x に 10 を代入し,19 行目で x を y に代入しています.期待したように,x 及び y の値は 10 になっています(出力結果の 1,2 行目).さらに,23 行目において,変数 y に 20 を代入しています.その結果,変数 y の値は 20 になります(出力結果の 4 行目)が,変数 x の値は 10 のままです(出力結果の 3 行目).

  次に,クラスのオブジェクトに対して同じようなことを行っています.15 行目で Complex クラスのオブジェクト c1 を生成し,18 行目で c1 を c2 に代入しています.期待したように,c1 及び c2 の各変数の値は 1,2 になっています(出力結果の 1,2 行目).さらに,21,22 行目において,c2 の各変数の値を 3,4 に変更しています.その結果,c2 の各変数の値が変更される(出力結果の 4 行目)と共に,c1 の各変数の値も変更されます(出力結果の 3 行目).

  なぜ,このような違いが現れるのでしょうか.それを明らかにするために,C++ で同様のプログラムを書いたのが以下に示す例です.

01	#include <stdio.h>
02
03	class Complex {
04		public :
05			double re, im;
06			Complex () {}   // コンストラクタ
07			Complex (double x, double y)   // コンストラクタ
08			{
09				re = x;
10				im = y;
11			}
12	};   // セミコロンを忘れないこと
13	
14	int main()
15	{
16		Complex *c1 = new Complex(1, 2);   // コンストラクタにより,変数 re,im に 1,2 を設定
17		Complex c3  = Complex(1, 2);   // コンストラクタにより,変数 re,im に 1,2 を設定
18		double x = 10.0;
19		printf("変更前 c1 = %f %f c3 = %f %f x = %f\n", c1->re, c1->im, c3.re, c3.im, x);
20		Complex *c2 = c1;
21		Complex c4  = c3;
22		double y = x;
23		printf("変更前 c2 = %f %f c4 = %f %f y = %f\n", c2->re, c2->im, c4.re, c4.im, y);
24		c2->re = 3.0;
25		c2->im = 4.0;
26		c4.re = 3.0;
27		c4.im = 4.0;
28		y = 20.0;
29		printf("変更後 c1 = %f %f c3 = %f %f x = %f\n", c1->re, c1->im, c3.re, c3.im, x);
30		printf("変更後 c2 = %f %f c4 = %f %f y = %f\n", c2->re, c2->im, c4.re, c4.im, y);
31		return 0;
32	}
		
  このプログラムを実行すると,以下に示すような出力が得られます.
	変更前 c1 = 1.000000 2.000000 c3 = 1.000000 2.000000 x = 10.000000
	変更前 c2 = 1.000000 2.000000 c4 = 1.000000 2.000000 y = 10.000000
	変更後 c1 = 3.000000 4.000000 c3 = 1.000000 2.000000 x = 10.000000
	変更後 c2 = 3.000000 4.000000 c4 = 3.000000 4.000000 y = 20.000000			
  このプログラムでは,クラスのオブジェクトのアドレスをポインタ変数に代入した場合( c1,c2 )とそうでない場合( c3,c4 )に分けて記述しています.まず,18,22 行目で定義された double 型の変数 x,y に関しては,Java の場合と同様の結果が得られています.また,c3,c4 に関しても,double 型変数 x,y と同様の結果になっています.これは,21 行目の代入文によって,22 行目の代入文と同様,c3 の内容がコピーされ,c4 に代入されているからです.つまり,x と y との関係と同様,c3 と c4 は全く別の内容を示す変数になっているからです.ただし,プログラム例 6.7 に示したように,クラス内で new 演算子を使用すると,new 演算子が指すデータは共通となり,片方のオブジェクトの値を変更すれば,他のオブジェクトの値も変化します.

  しかし,ポインタを使用した場合はどうでしょうか.20 行目の代入文によって,ポインタがコピーされて代入されることになりますので,c1 と c2 は,同じオブジェクトを指すことになります.その結果,出力結果の 3,4 行目に示すように,c2 の各変数を変更すれば,c1 の各変数も変更されることになります(逆も同様).

  上の例からも明らかなように,Java の場合は,C++ におけるポインタを使用した場合と一致しています.つまり,Java の場合,int や double のような基本データ型とクラスのオブジェクトの処理が異なっていることになります.これを知らないでプログラムを書けばとんでもないことになりかねませんので,注意して下さい.

(プログラム例 7.6 ) private 変数 

  クラスの宣言と private 変数の特徴に関する例です.

/****************************/
/* private 変数             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

/***********************/
/* クラスExampleの定義 */
/***********************/
class Example {

	private int x;   // private 変数
	int y;   // 変数
					// メソッド,値の設定
	void v_set1()
	{
		x = 10;   // クラス内のメソッドからは参照可能
		y = 20;
	}
					// メソッド,値の設定
	void v_set2()
	{
		x = 30;   // クラス内のメソッドからは参照可能
		y = 40;
	}
					// メソッド,値の出力
	void output()
	{
		System.out.println("   x = " + x + ",  y = " + y);
	}
}

/**************************************/
/* mainメソッドを含むクラスTestの定義 */
/**************************************/
public class Test {
	public static void main(String args[]) throws IOException
	{
		Example t1 = new Example();   // Example 型オブジェクトの生成
		                              // 下の 2 行のように書いても良い
		Example t2;   // Example 型オブジェクトであることの宣言
		t2 = new Example();   // Example 型オブジェクトの生成

		t1.v_set1();   // メソッドの呼び出し
		t2.v_set2();   // メソッドの呼び出し

		System.out.println("オブジェクト t1");
		System.out.println("   y = " + t1.y);   // 変数 y の参照,変数 x の参照はできない
		t1.output();
		System.out.println("オブジェクト t2");
		System.out.println("   y = " + t2.y);   // 変数 y の参照,変数 x の参照はできない
		t2.output();
	}
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
オブジェクト t1
  y = 20
  x = 10,  y = 20
オブジェクト t2
  y = 40
  x = 30,  y = 40		

(プログラム例 7.7 ) 静的変数・メソッドとインスタンス変数・メソッド 

  静的変数・メソッドとインスタンス変数・メソッドの使用例です.

/****************************/
/* final,static            */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

/****************************/
/* クラスFinalExampleの定義 */
/****************************/
final class FinalExample   // このクラスを拡張できない(継承できない)
{
	static String msg = "final class";   // 静的変数
					// インスタンスメソッド
	void func()
	{
		System.out.println("FinalExample のインスタンスメソッド");
	}
					// 静的メソッド
	static void static_func()
	{
		System.out.println("FinalExample の静的メソッド");
	}
}

/***********************/
/* クラスExampleの定義 */
/***********************/
class Example
{
	static int count;   // 静的変数
	final int x = 10;   // 定数,値を変更できない
	int y = 20;   // インスタンス変数
					// コンストラクタ,インスタンスが生成される度に count を増加
	Example ()
	{
		count++;
	}
					// インスタンスメソッド
	void func()
	{
		System.out.println("インスタンスメソッド");
	}
					// 静的メソッド
	static void static_func()
	{
		System.out.println("静的メソッド");
	}
}

/**************************************/
/* mainメソッドを含むクラスTestの定義 */
/*************************************/
public class Test {
	public static void main(String args[]) throws IOException
	{
					// final クラス
		System.out.println("   *** final クラス ***");
		FinalExample fe = new FinalExample();
		System.out.println(fe.msg);
		System.out.println(FinalExample.msg);   // 静的変数はこのように参照するのが一般的
		fe.func();   // インスタンスメソッドの呼び出し
		fe.static_func();   // 静的メソッドの呼び出し
		FinalExample.static_func();   // 静的メソッドの呼び出し(こちらの方が一般的)
					// final でないクラス
		System.out.println("   *** final でないクラス ***");
		Example.count = 0;
		Example ex1 = new Example();   // count の値は 1 になる
		Example ex2 = new Example();   // count の値は 2 になる
//		ex2.x = 100;   // 値を変更することは出来ない
		ex2.y = 200;
		System.out.println("x = " + ex1.x + " y = " + ex1.y + " count = " + Example.count);
		System.out.println("x = " + ex2.x + " y = " + ex2.y + " count = " + Example.count);
		ex2.func();   // インスタンスメソッドの呼び出し
		Example.static_func();   // 静的メソッドの呼び出し
					// クラス Test 内のメソッドの利用
		System.out.println("   *** クラス Test 内のメソッド ***");
//		message();   // インスタンスメソッドの呼び出し.main メソッド(このメソッド)は
		             // static であるので static でないメソッドを直接呼び出せない
		static_message();   // 静的メソッドの呼び出し
	}
					// インスタンスメソッド
	void message ()
	{
		System.out.println("main から インスタンスメソッドの呼び出し");
	}
					// 静的メソッド
	static void static_message ()
	{
		System.out.println("main から 静的メソッドの呼び出し");
	}
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
   *** final クラス ***
final class
final class
FinalExample のインスタンスメソッド
FinalExample の静的メソッド
FinalExample の静的メソッド
   *** final でないクラス ***
x = 10 y = 20 count = 2
x = 10 y = 200 count = 2
インスタンスメソッド
静的メソッド
   *** クラス Test 内のメソッド ***
main から 静的メソッドの呼び出し
		

7.3 コンストラクタ 

  コンストラクタ構築子constructor )は,オブジェクトを初期化する目的で作成するクラスと同じ名前を持った特別なメソッドです.コンストラクタを持つクラスのオブジェクトを生成すると,コンストラクタが自動的に呼び出され,オブジェクトを初期化します.

  コンストラクタはメソッドですから,通常,引数によって初期設定に必要なデータを与えてやる必要があります.ただし,コンストラクタが定義されていない場合や引数のないコンストラクタが定義されている場合は別です.クラスにおいてコンストラクタを全く定義しなかったり,または,定義しても変数に対する初期設定を行わないと,デフォルトのコンストラクタによってオブジェクトの各変数は初期化されます(初期設定が行われた変数を除く).原則として,基本的なデータ型については 0 (論理型は false ),そうでないものは null に初期化されます.

  Java においては,C/C++ にあるデストラクタというものは存在しません.また,コンストラクタには,他のメソッドと同じように,pubulic,protected,または,private という修飾子を付加することができます.

  プログラム例 7.6 においては,Example 型オブジェクト t1,および,t2 を定義した後,
t1.v_set1();
t2.v_set2();		
とうい方法で,インスタンスメソッド v_set1,および,v_set2 を呼び出し,クラス内に定義された変数(フィールド) x,y の値を設定していました.しかし,メソッド v_set1,および,v_set2 の代わりに,
Example (int x, int y)   // コンストラクタ
{
	this.x = x;
	this.y = y;
}		
のようなコンストラクタを用意しておけば,
Example t1 = new Example(10, 20);   // Example 型オブジェクトの生成
                                    // 下の 2 行のように書いても良い
Example t2;   // Example 型オブジェクトであることの宣言
t2 = new Example(30, 40);   // Example 型オブジェクトの生成		
という記述だけで,オブジェクトの定義と初期設定が済んでしまいます.なお,this は,オブジェクト自身を示す演算子(オブジェクト自身に対するポインタ)であり,「 this.x 」は,クラス Example 内に「 private int x; 」と定義されている変数 x を指します.なお,
Example(int x1, int y1)
{
	x = x1;
	y = y1;
}		
のように,引数の名前を変えてやれば,this を使用せずに記述することも可能です.

(プログラム例 7.8 ) コンストラクタ 

  基本的なコンストラクタの使用例です.

01	/****************************/
02	/* 時間データの処理         */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	/********************/
08	/* クラスTimeの宣言 */
09	/********************/
10	class Time {
11		int hour, min, sec;
12					// コンストラクタ1
13		Time(int h, int m, int s)
14		{
15			hour = h;
16			min  = m;
17			sec  = s;
18		}
19					//  コンストラクタ2
20		Time(int h, int m)
21		{
22			hour = h;
23			min  = m;
24		}
25					//  コンストラクタ3
26		Time() {}
27	}
28	
29	/**************************************/
30	/* mainメソッドを含むクラスTestの定義 */
31	/**************************************/
32	public class Test {
33		public static void main(String args[]) throws IOException
34		{
35			Time t1 = new Time(10, 20, 23);   // 10:20:23
36			System.out.println(t1.hour + ":" + t1.min + ":" + t1.sec);
37			Time t2 = new Time(12, 30);       // sec は 0 に初期設定される
38			System.out.println(t2.hour + ":" + t2.min + ":" + t2.sec);
39			Time t3 = new Time();            // hour,min,sec がすべて 0 に設定される
40			System.out.println(t3.hour + ":" + t3.min + ":" + t3.sec);
41		}
42	}
		
13 行目~ 18 行目

  3 つの引数を持つコンストラクタの定義です.35 行目のように,3 つの引数を記述してインスタンスを生成すると,このコンストラクタが呼ばれます.

20 行目~ 24 行目

  2 つの引数を持つコンストラクタの定義です.37 行目のように,2 つの引数を記述してインスタンスを生成すると,このコンストラクタが呼ばれます.変数 sec の初期設定が行われていませんので,変数 sec に対しては,自動的に 0 が設定されます.Java には,C++ のようなデフォルト引数という機能がありませんので,このようなコンストラクタも必要になります.後に述べる一般的なメソッドでも同様ですが,同じ名前のメソッドであっても,引数として渡されるデータの型が異なったり,引数の数が異なれば異なるメソッドとみなされ,引数の型及び数に対応して適切なメソッドが呼ばれます.これを,メソッドのオーバーロード多重定義)と呼びます.

26 行目

  引数を持たないコンストラクタの定義です.39 行目のように,引数を記述しないでインスタンスを生成すると,このコンストラクタが呼ばれます.変数 hour,min,sec は,すべて,0 に初期設定されます.このプログラムにおいて,26 行目を記述しないで,39 行目を記述すればエラーになりますが,コンストラクタを全く記述しないで,39 行目を記述した場合は,26 行目のコンストラクタと同様の処理を行ってくれます.勿論,その場合は,35 行目,37 行目のような記述は出来ません.
  このプログラムによって,以下に示すような結果が得られます.
10:20:23
12:30:0
0:0:0		

7.4 メソッドとのデータの受け渡し

  メソッドの基本的な部分については 7.1 節において説明しましたが,ここでは,もう少し細部について,例を使用しながら説明していきます.

(プログラム例 7.9 ) 様々なデータ型の引き渡し 

  この例では,44 行目に見るように,4 種類のデータをメソッド method に引数として渡しています.

01	/****************************/
02	/* 様々なデータ型の引き渡し */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	/***********************/
08	/* クラスComplexの定義 */
09	/***********************/
10	class Complex {
11		double re, im;
12							 // コンストラクタ
13		Complex(double re, double im)
14		{
15			this.re = re;
16			this.im = im;
17		}
18	}
19	
20	/**************************************/
21	/* mainメソッドを含むクラスTestの定義 */
22	/**************************************/
23	public class Test {
24		public static void main(String args[])
25		{
26			int a = 1;
27			int ar_1 [] = {10, 20};
28			int ar_2 [][] = {{100, 200, 300}, {400, 500, 600}};
29			Complex cx = new Complex(1000, 2000);
30						// メソッドを呼ぶ前の状態
31			System.out.println("   ***メソッドを呼ぶ前の状態***");
32			System.out.println("a = " + a);
33			System.out.println("ar_1[0] = " + ar_1[0] + ", ar_1[1] = " + ar_1[1]);
34			for (int i1 = 0; i1 < 2; i1++) {
35				for (int i2 = 0; i2 < 3; i2++) {
36					if (i2  < 2)
37						System.out.print("ar_2[" + i1 + "][" + i2 + "] = " + ar_2[i1][i2] + ", ");
38					else
39						System.out.println("ar_2[" + i1 + "][" + i2 + "] = " + ar_2[i1][i2]);
40				}
41			}
42			System.out.println("cx.re = " + cx.re + ", cx.im = " + cx.im);
43						// メソッドを呼ぶ
44			method(a, ar_1, ar_2, cx);
45						// メソッドを呼んだ後の状態
46			System.out.println("   ***メソッドを呼んだ後の状態***");
47			System.out.println("a = " + a);
48			System.out.println("ar_1[0] = " + ar_1[0] + ", ar_1[1] = " + ar_1[1]);
49			for (int i1 = 0; i1 < 2; i1++) {
50				for (int i2 = 0; i2 < 3; i2++) {
51					if (i2  < 2)
52						System.out.print("ar_2[" + i1 + "][" + i2 + "] = " + ar_2[i1][i2] + ", ");
53					else
54						System.out.println("ar_2[" + i1 + "][" + i2 + "] = " + ar_2[i1][i2]);
55				}
56			}
57			System.out.println("cx.re = " + cx.re + ", cx.im = " + cx.im);
58		}
59	
60		/******************************************/
61		/* メソッドの例                           */
62		/*      a : int 型                        */
63		/*      ar_1 : int 型 1 次元配列          */
64		/*      ar_2 : int 型 2 次元配列          */
65		/*      cx : Complex クラスのオブジェクト */
66		/******************************************/
67		static void method(int a, int ar_1[], int ar_2[][], Complex cx)
68		{
69			a = 9;
70			ar_1[0] = 99;
71			ar_2[1][0] = 999;
72			cx.im = 9999;
73		}
74	}
		
1 番目の引数 a

  26 行目で設定された a の値が,67 行目の a にコピーされ,メソッド method に渡されます.69 行目において a の値を変更しても,コピーが変更されただけですから,main メソッド内の a の値はそのままです(出力結果の 08 行目参照).

2 番目の引数 ar_1

  1 次元配列の受け渡しです.基本的に,C/C++ の場合と同様,記憶領域の先頭のアドレスがコピーされてメソッドに渡されます.そのため,method メソッド内において,そのアドレスが指す場所の値を変更すれば,main メソッド内の配列の値も変更されます(出力結果の 09 行目参照).C/C++ に対するプログラム例における 5 番目の引数に相当する方法です.

3 番目の引数 ar_2

  2 次元配列の受け渡しです.1 次元配列と同様,method メソッド内において,そのアドレスが指す場所の値を変更すれば,main メソッド内の配列の値も変更されます(出力結果の 10,11 行目参照).C/C++ に対するプログラム例における 7 番目の引数に相当する方法です.

4 番目の引数 cx

  Complex クラスのオブジェクトの受け渡しです.オブジェクトのアドレスが渡されるため,method メソッド内において,そのアドレスが指す場所の値を変更すれば,main メソッド内の配列の値も変更されます(出力結果の 12 行目参照).C/C++ に対するプログラム例における 10,または,11 番目の引数に相当する方法です.
  このプログラムによって,以下に示すような結果が得られます(行番号は説明用に追加).
01	   ***メソッドを呼ぶ前の状態***
02	a = 1
03	ar_1[0] = 10, ar_1[1] = 20
04	ar_2[0][0] = 100, ar_2[0][1] = 200, ar_2[0][2] = 300
05	ar_2[1][0] = 400, ar_2[1][1] = 500, ar_2[1][2] = 600
06	cx.re = 1000.0, cx.im = 2000.0
07	   ***メソッドを呼んだ後の状態***
08	a = 1
09	ar_1[0] = 99, ar_1[1] = 20
10	ar_2[0][0] = 100, ar_2[0][1] = 200, ar_2[0][2] = 300
11	ar_2[1][0] = 999, ar_2[1][1] = 500, ar_2[1][2] = 600
12	cx.re = 1000.0, cx.im = 9999.0
		

(プログラム例 7.10 ) 複数結果の受け取り 

  基本的に,メソッドは,一つのデータを戻り値として返すだけです.メソッドの実行結果として,複数個のデータを得たいような場合はどのようにすれば良いでしょうか.一つの方法は,プログラム例 7.9 からも明らかなように,引数として配列を渡してやることです.また,すべてのデータが同じ型であれば,配列(に対するアドレス)を戻り値に設定することも可能です.次のプログラムでは,2 つの int 型データの和と差を引数内の int 型配列に入れ,また,積と商を double 型配列に入れ,それを戻り値としています.

/****************************/
/* 複数結果の受け取り       */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

/**************************************/
/* mainメソッドを含むクラスTestの定義 */
/**************************************/
public class Test {
	public static void main(String args[])
	{
		int a = 10, b = 20;
		int wa_sa [] = new int [2];
					// メソッドを呼ぶ
		double seki_sho [] = cal(a, b, wa_sa);
					// 結果
		System.out.println(a + " と " + b + " の");
		System.out.println("   和は " + wa_sa[0]);
		System.out.println("   差は " + wa_sa[1]);
		System.out.println("   積は " + seki_sho[0]);
		System.out.println("   商は " + seki_sho[1]);
	}

	/******************************/
	/* 2 つのデータの加減乗除     */
	/*      a, b : データ         */
	/*      wa_sa : a+b, a-b      */
	/*      	return : a*b, a/b */
	/******************************/
	static double [] cal(int a, int b, int wa_sa [])
	{
		wa_sa[0] = a + b;
		wa_sa[1] = a - b;
		double seki_sho [] = new double [2];
		seki_sho[0] = a * b;
		seki_sho[1] = (double)a / b;
		return seki_sho;
	}
}
		

(プログラム例 7.11 ) メソッド名の引き渡し

  場合によっては,メソッド名を引数としたい場合があります.例えば,非線形方程式の根を求めるメソッドを作成したいとします.また,根を求めるアルゴリズム自体は,対象とする方程式が異なっても変わらないとします.このようなとき,方程式の計算を別のメソッドで行い,そのメソッド名を根を求めるメソッドに受け渡すように作成すれば,方程式が変わっても,同じ根を求めるメソッドを利用できます.

  例えば,C/C++ において関数名を引数とするには,基本的に,関数のアドレスを使用すれば可能です.例えば,
int (*sub)(double, char *)		
という記述は,関数 sub が,double 及び char に対するポインタという 2 つの引数をもち,int を返す関数へのアドレスであることを表しています.

  次のプログラムにおける関数 newton は,ニュートン法により非線形方程式 f(x) = 0 ,この例では,
f1(x) = ex - 3x = 0 と
f2(x) = sin(x) - 0.5 = 0		
の解を求めるためのものです.与えられた x における f(x) の値及び f(x) の微分値を計算する関数( 012 行目~ 034 行目.f1(x) に対しては snx1 と dsnx1,また,f2(x) に対しては snx2 と dsnx2 )名を引数とし( 094,099 行目),ニュートン法を実行する関数 newton は,対象とする方程式が異なっても全く修正していません.ただし,ニュートン法とは,f(x) が単調連続で変曲点が無く,かつ,微分可能であるとき利用できる方法であり,根の適当な初期値 x0 から始めて,反復公式
xn+1 = xn - f(xn) / f'(xn)		
を繰り返すことによって,非線形方程式の解を求める方法です.

001	/****************************/
002	/* 関数名の受け渡し         */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	#include <stdio.h>
006	#include <math.h>
007	
008	/****************************/
009	/* 関数値及びその微分の計算 */
010	/****************************/
011						// f(x) = sin(x) - 0.5
012	double snx1(double x)
013	{
014		double y= exp(x) - 3.0 * x;
015		return y;
016	}
017	
018	double dsnx1(double x)
019	{
020		double y = exp(x) - 3.0;
021		return y;
022	}
023						// f(x) = sin(x) - 0.5
024	double snx2(double x)
025	{
026		double y= sin(x) - 0.5;
027		return y;
028	}
029	
030	double dsnx2(double x)
031	{
032		double y = cos(x);
033		return y;
034	}
035	
036	/*****************************************************/
037	/* Newton法による非線形方程式(f(x)=0)の解            */
038	/*      fn : f(x)を計算する関数名                    */
039	/*      dfn : f(x)の微分を計算する関数名             */
040	/*      x0 : 初期値                                  */
041	/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
042	/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
043	/*      max : 最大試行回数                           */
044	/*      ind : 実際の試行回数                         */
045	/*            (負の時は解を得ることができなかった) */
046	/*      return : 解                                  */
047	/*****************************************************/
048	double newton(double(*f)(double), double(*df)(double), double x0,
049				  double eps1, double eps2, int max, int *ind)
050	{
051		double x1 = x0;
052		double x  = x1;
053		int sw    = 0;
054		*ind      = 0;
055	
056		while (sw == 0 && *ind >= 0) {
057	
058			sw       = 1;
059			*ind    += 1;
060			double g = f(x1);
061	
062			if (fabs(g) > eps2) {
063				if (*ind <= max) {
064					double dg = df(x1);
065					if (fabs(dg) > eps2) {
066						x = x1 - g / dg;
067						if (fabs(x-x1) > eps1 && fabs(x-x1) > eps1*fabs(x)) {
068							x1 = x;
069							sw = 0;
070						}
071					}
072					else
073						*ind = -1;
074				}
075				else
076					*ind = -1;
077			}
078		}
079	
080		return x;
081	}
082	
083	/*******************/
084	/* main プログラム */
085	/*******************/
086	int main()
087	{
088		double eps1 = 1.0e-7;
089		double eps2 = 1.0e-10;
090		double x0 = 0.0;
091		int max = 30;
092		int ind;
093						// f(x) = sin(x) - 0.5 = 0 の解
094		double x = newton(snx1, dsnx1, x0, eps1, eps2, max, &ind);
095	
096		printf("exp(x) - 3.0 * x = 0\n");
097		printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind, x, snx1(x), dsnx1(x));
098						// f(x) = sin(x) - 0.5 = 0 の解
099		x = newton(snx2, dsnx2, x0, eps1, eps2, max, &ind);
100	
101		printf("sin(x) - 0.5 = 0\n");
102		double PI = 4.0 * atan(1.0);
103		printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind, 180*x/PI, snx2(x), dsnx2(x));
104	
105		return 0;
106	}
		

  このプログラムを実行すると,以下に示すような結果が得られます.
exp(x) - 3.0 * x = 0
   ind = 5, x = 0.619, f(x) = 0.000, f'(x) = -1.143
sin(x) - 0.5 = 0
   ind = 4, x = 30.000, f(x) = -0.000, f'(x) = 0.866		
  Java においては,ポインタが明示的に定義されていないため,C/C++ と全く同じ方法では不可能です.少なくとも,今まで例に示した Java のプログラムのように,クラス Test の中に newton,snx,dsnx を記述し,かつ,メソッド名(またはそれに変わる情報)を受け渡してやることは不可能だと思います.もちろん,ニュートン法で呼ぶメソッド名を固定し,異なる非線形関数を扱いたいときは,そのメソッドの内容を修正してやれば可能です.しかし,上に示した C/C++ のプログラムのように,同じメソッド内で複数の非線形方程式の解を求めたいような場合は対応できません.Math クラスの sin(x) や sqrt(x) のように,引数を変えるだけで対応できることが好ましいと思います.

  以下に示すプログラムにおいては,ニュートン法を実行するメソッドを,Math クラスと同様,「 public final 」指定をしたクラス Math_ex 内の static クラスとして定義しています.メソッド名を引数として渡す代わりに,f(x) の値やその微分値を計算するクラス Func を定義し,そのオブジェクトを渡しています.どの f(x) 等の値を計算するかは,Func クラスのインスタンスを生成する際の引数で制御しています( 17,18,26,27 行目.生成するインスタンス毎に,クラス Func 内の変数 sw の値が異なってくる).常に,クラス名を Func,メソッド名を snx にしなければならない等の制約はありますが,C/C++ における方法と似ていますし,Math クラスのメソッドのような感覚で使用できると思います.

01	/****************************/
02	/* ニュートン法             */
03	/*      coded by Y.Suganuma */
04	/****************************/
05	import java.io.*;
06	
07	public class Test {
08		public static void main(String args[]) throws IOException
09		{
10			double eps1 = 1.0e-7;
11			double eps2 = 1.0e-10;
12			double x0 = 0.0;
13			int max = 30;
14			int ind[] = new int [1];
15						// f(x) = exp(x) - 3.0 * x = 0 の解
16								// 関数値を計算するクラス
17			Func kn1 = new Func(1);
18			Func kn2 = new Func(2);
19								// ニュートン法の実行
20			double x = Math_ex.newton(x0, eps1, eps2, max, ind, kn1, kn2);
21								// 出力
22			System.out.printf("exp(x) - 3.0 * x = 0\n");
23			System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], x, kn1.snx(x), kn2.snx(x));
24						// f(x) = sin(x) - 0.5 = 0 の解
25								// 関数値を計算するクラス
26			Func kn3 = new Func(3);
27			Func kn4 = new Func(4);
28								// ニュートン法の実行
29			x = Math_ex.newton(x0, eps1, eps2, max, ind, kn3, kn4);
30								// 出力
31			System.out.printf("sin(x) - 0.5 = 0\n");
32			System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], 180*x/Math.PI, kn3.snx(x), kn4.snx(x));
33		}
34	}
35	
36	/******************************/
37	/* 関数値およびその微分の計算 */
38	/******************************/
39	class Func {
40		private int sw;
41						// コンストラクタ
42		Func (int s) {sw = s;}
43						// double型メソッド
44		double snx(double x)
45		{
46	
47			double y = 0.0;
48	
49			switch (sw) {
50							// 関数 f(x) = exp(x) - 3.0 * x の計算
51				case 1:
52					y = Math.exp(x) - 3.0 * x;
53					break;
54							// 関数 f(x) = exp(x) - 3.0 * x の微分の計算
55				case 2:
56					y = Math.exp(x) - 3.0;
57					break;
58							// 関数 f(x) = Math.sin(x) - 0.5 の計算
59				case 3:
60					y = Math.sin(x) - 0.5;
61					break;
62							// 関数 f(x) = Math.sin(x) - 0.5 の微分の計算
63				case 4:
64					y = Math.cos(x);
65					break;
66			}
67	
68			return y;
69		}
70	}

--------------------------------

/************************
/* 科学技術計算用の手法 */
/************************/
public final class Math_ex {

	/*****************************************************/
	/* Newton法による非線形方程式(f(x)=0)の解            */
	/*      x1 : 初期値 	                             */
	/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
	/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
	/*      max : 最大試行回数                           */
	/*      ind : 実際の試行回数                         */
	/*            (負の時は解を得ることができなかった) */
	/*      kn1 : 関数を計算するクラスオブジェクト       */
	/*      kn2 : 関数の微分を計算するクラスオブジェクト */
	/*      return : 解                                  */
	/*****************************************************/
	static double newton(double x1, double eps1, double eps2, int max,
                         int ind[], Func kn1, Func kn2)
	{
		double x = x1;
		int sw   = 0;
		ind[0]   = 0;

		while (sw == 0 && ind[0] >= 0) {

			ind[0]++;
			sw       = 1;
			double g = kn1.snx(x1);

			if (Math.abs(g) > eps2) {
				if (ind[0] <= max) {
					double dg = kn2.snx(x1);
					if (Math.abs(dg) > eps2) {
						x = x1 - g / dg;
						if (Math.abs(x-x1) > eps1 && Math.abs(x-x1) > eps1*Math.abs(x)) {
							x1 = x;
							sw = 0;
						}
					}
					else
						ind[0] = -1;
				}
				else
					ind[0] = -1;
			}
		}

		return x;
	}
}
		
  このプログラムを実行すると,以下に示すような結果が得られます.
exp(x) - 3.0 * x = 0
   ind = 5, x = 0.619, f(x) = -0.000, f'(x) = -1.143
sin(x) - 0.5 = 0
   ind = 4, x = 30.000, f(x) = -0.000, f'(x) = 0.866		
  たしかに,上に述べた方法によっても,C/C++ と似たような結果になりました.しかし,関数値等を計算するクラス Func を記述するのに,本来,目的とする計算式だけでよいはずですが,switch 文を使用しなければならず,多少複雑になります.さらに,上で述べた方法には大きな問題があります.クラス Math_ex 内で,メソッド名を必要としないメソッドと必要とするメソッドを定義した場合,メソッド名を必要としないメソッドだけを利用する場合においても,クラス Func を定義しておく必要があります.また,メソッド名を必要とするメソッドにおいて,異なる引数や戻り値を持つ snx 等のメソッドが必要になった場合は,いずれのメソッドを利用する際にも,それらすべてに対応できるようにクラス Func を作成しておく必要があります.

  そこで,次の方法は,以下に示すようにニュートン法を記述してあるクラスを継承して,f(x) 等を計算するクラスを定義する方法です(継承に関しては,次章を参照して下さい).このようにすれば,異なる非線形方程式の解を同じプログラム内で計算しようとする場合は,対象とする非線形方程式ごとに異なるクラスを定義することによって可能となります.

/****************************/
/* ニュートン法             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
		double eps1 = 1.0e-7;
		double eps2 = 1.0e-10;
		double x0   = 0.0;
		int max     = 30;
		int ind[]   = new int [1];
					// f(x) = exp(x) - 3.0 * x = 0 の解
							// 関数値を計算するクラス
		Kansu1 kn1 = new Kansu1();
							// ニュートン法の実行
		double x = kn1.newton(x0, eps1, eps2, max, ind);
							// 出力
		System.out.printf("exp(x) - 3.0 * x = 0\n");
		System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], x, kn1.snx(x), kn1.dsnx(x));
					// f(x) = sin(x) - 0.5 = 0 の解
							// 関数値を計算するクラス
		Kansu2 kn2 = new Kansu2();
							// ニュートン法の実行
		x = kn2.newton(x0, eps1, eps2, max, ind);
							// 出力
		System.out.printf("sin(x) - 0.5 = 0\n");
		System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], 180*x/Math.PI, kn2.snx(x), kn2.dsnx(x));
	}
}

/******************************/
/* 関数値およびその微分の計算 */
/******************************/
			// f(x) = exp(x) - 3.0 * x = 0 の解
class Kansu1 extends Newton   // クラスNewtonを継承
{
					// 関数値(f(x))の計算
	double snx(double x)   // クラスNewtonのメソッドのオーバーライド
	{
		double y = Math.exp(x) - 3.0 * x;
		return y;
	}
					// 関数の微分の計算
	double dsnx(double x)   // クラスNewtonのメソッドのオーバーライド
	{
		double y = Math.exp(x) - 3.0;
		return y;
	}
}
			// f(x) = sin(x) - 0.5 = 0 の解
class Kansu2 extends Newton   // クラスNewtonを継承
{
					// 関数値(f(x))の計算
	double snx(double x)   // クラスNewtonのメソッドのオーバーライド
	{
		double y = Math.sin(x) - 0.5;
		return y;
	}
					// 関数の微分の計算
	double dsnx(double x)   // クラスNewtonのメソッドのオーバーライド
	{
		double y = Math.cos(x);
		return y;
	}
}

/*****************************************************/
/* Newton法による非線形方程式(f(x)=0)の解            */
/*      x1 : 初期値                                  */
/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
/*      max : 最大試行回数                           */
/*      ind : 実際の試行回数                         */
/*            (負の時は解を得ることができなかった) */
/*      return : 解                                  */
/*****************************************************/
abstract class Newton {

	abstract double snx(double x);   // 定義しておく必要あり
	abstract double dsnx(double x);   // 定義しておく必要あり

	double newton(double x1, double eps1, double eps2, int max, int ind[])
	{
		double x = x1;
		int sw   = 0;
		ind[0]   = 0;

		while (sw == 0 && ind[0] >= 0) {

			ind[0]++;
			sw       = 1;
			double g = snx(x1);

			if (Math.abs(g) > eps2) {
				if (ind[0] <= max) {
					double dg = dsnx(x1);
					if (Math.abs(dg) > eps2) {
						x = x1 - g / dg;
						if (Math.abs(x-x1) > eps1 && Math.abs(x-x1) > eps1*Math.abs(x)) {
							x1 = x;
							sw = 0;
						}
					}
					else
						ind[0] = -1;
				}
				else
					ind[0] = -1;
			}
		}

		return x;
	}
}
		

  最後に,もう一つの方法について考えてみます.それは,ラムダ式lambda )の利用です.ラムダ式は,
インタフェース名 ラムダ式名 = ( 引数 ) -> { 処理 }		
のような形をしており,関数型インターフェースの抽象メソッドを実装する際に処理内容をラムダ式で記述することができます.ここで,関数型インターフェースとは,
java.lang.Comparable
java.lang.Runnable		
などのように,抽象メソッドを 1 つだけ持つインターフェースのことです.例えば,Ruunable インタフェース( run という抽象メソッドを一つだけ持つ)を利用して,以下に示すようなラムダ式を定義可能です.
public class Test {
	public static void main(String args[])
	{
		Runnable lam = () -> { System.out.println("Runnable を利用したラムダ式"); };
		lam.run();   // 「Runnable を利用したラムダ式」を出力
	}
}		
  勿論,自分で作成したインタフェースを利用して,ラムダ式を定義することも可能です.下に示す例においては,メソッド func(インタフェース内のメソッドは,全て,抽象クラスとみなされる)を持ったインタフェース add_sub を定義し,そのインタフェースを利用して,2 つのラムダ式を定義しています.それらのラムダ式を,メソッド sub の引数とし,希望する結果を得ています.
public class Test {
	public static void main(String args[])
	{
			// ラムダ式の定義
		add_sub it_add = (x, y) -> { return x + y; };   // 既に定義されている変数は不可
		add_sub it_sub = (x, y) -> { return x - y; };   // 既に定義されている変数は不可
			// ラムダ式をメソッドの引数
		sub(it_add);   // 13 を出力
		sub(it_sub);   // -3 を出力
	}

	static void sub(add_sub it) {
		System.out.println(it.func(5, 8));
	}
}
			// インタフェース add_sub
interface add_sub {
	public int func(int x, int y);   // 抽象メソッド
}
		
  下に示すのは,上で述べた方法を利用して,ニュートン法に対するプログラムを記述した例です.記述方法に多少の違いはありますが,「 func 」という名前か固定されており,最初に示したクラスのオブジェクトを引数にした場合と大きな差はありません.
/****************************/
/* ニュートン法             */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;
			// main
public class Test {
	public static void main(String args[]) throws IOException
	{
		double eps1 = 1.0e-7;
		double eps2 = 1.0e-10;
		double x0 = 0.0;
		int max = 30;
		int ind[] = new int [1];
				// f(x) = exp(x) - 3.0 * x = 0 の解
					// 関数値を計算するラムダ式の定義
		diff kn1 = (x1) -> { return Math.exp(x1) - 3.0 * x1; };
		diff kn2 = (x1) -> { return Math.exp(x1) - 3.0; };
					// ニュートン法の実行
		double x = Math_ex.newton(x0, eps1, eps2, max, ind, kn1, kn2);
					// 出力
		System.out.printf("exp(x) - 3.0 * x = 0\n");
		System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], x, kn1.func(x), kn2.func(x));
				// f(x) = sin(x) - 0.5 = 0 の解
					// 関数値を計算するラムダ式の定義
		diff kn3 = (x1) -> { return Math.sin(x1) - 0.5; };
		diff kn4 = (x1) -> { return Math.cos(x1); };
					// ニュートン法の実行
		x = Math_ex.newton(x0, eps1, eps2, max, ind, kn3, kn4);
					// 出力
		System.out.printf("sin(x) - 0.5 = 0\n");
		System.out.printf("   ind = %d, x = %.3f, f(x) = %.3f, f'(x) = %.3f\n", ind[0], 180*x/Math.PI, kn3.func(x), kn4.func(x));
	}
}
			// インタフェース diff
interface diff {
	public double func(double x);   // 抽象メソッド
}

--------------------------------

/************************
/* 科学技術計算用の手法 */
/************************/
public final class Math_ex {

	/*****************************************************/
	/* Newton法による非線形方程式(f(x)=0)の解            */
	/*      x1 : 初期値 	                             */
	/*      eps1 : 終了条件1(|x(k+1)-x(k)|<eps1)   */
	/*      eps2 : 終了条件2(|f(x(k))|<eps2)       */
	/*      max : 最大試行回数                           */
	/*      ind : 実際の試行回数                         */
	/*            (負の時は解を得ることができなかった) */
	/*      kn1 : 関数を計算するラムダ式                 */
	/*      kn2 : 関数の微分を計算するラムダ式           */
	/*      return : 解                                  */
	/*****************************************************/
	static double newton(double x1, double eps1, double eps2, int max,
                         int ind[], diff kn1, diff kn2)
	{
		double x = x1;
		int sw   = 0;
		ind[0]   = 0;

		while (sw == 0 && ind[0] >= 0) {

			ind[0]++;
			sw       = 1;
			double g = kn1.func(x1);

			if (Math.abs(g) > eps2) {
				if (ind[0] <= max) {
					double dg = kn2.func(x1);
					if (Math.abs(dg) > eps2) {
						x = x1 - g / dg;
						if (Math.abs(x-x1) > eps1 && Math.abs(x-x1) > eps1*Math.abs(x)) {
							x1 = x;
							sw = 0;
						}
					}
					else
						ind[0] = -1;
				}
				else
					ind[0] = -1;
			}
		}

		return x;
	}
}
		

7.5 main メソッド

  main も一種のメソッドです.これまでのプログラムにおいて,
public static void main(String args[])		
と記述していた変数 args が main メソッドの引数です.今までは,この変数を全く利用してきませんでした.しかし,例えば,
java Test 2 3		
とキーボードから入力すると,2 つの値 2 と 3 の和を計算し,その結果をコンソールに出力したいような場合が存在します.このような場合,2 や 3 が main メソッドの引数とみなされます.これらのデータが,文字列として,String クラスのオブジェクトの配列 args に入ります.渡されたデータの数は,「args.length」という記述によって知ることができます.

(プログラム例 7.12 ) main メソッドの引数(数字の和) 

  次の例は,複数個の整数を加えるプログラムです.

/***********************************/
/* main メソッドの引数(数字の和) */
/*      coded by Y.Suganuma        */
/***********************************/
import java.io.*;

public class Test {
	public static void main(String args[]) throws IOException
	{
	/*
		 引数の内容の出力
	*/
		System.out.println("     引数の数 " + args.length);

		int sum = 0;
		for (int i1 = 0; i1 < args.length; i1++) {
			int k = Integer.parseInt(args[i1]);   // 文字を整数に変換
			System.out.println("     " + (i1+1) + " 番目の引数 " + k);
			sum += k;
		}
	/*
		 結果の表示
	*/
		System.out.println("結果=" + sum);
	}
}
		

  例えば,「java Test 2 3」と入力すると,以下のような出力が得られます.
     引数の数 2
     1 番目の引数 2
     2 番目の引数 3
結果=5		

7.6 パッケージ

  複数のクラスを一つのグループにまとめ,他のグループからそのグループに含まれるクラス等を隠す手段としてパッケージ定義があります.C++ に全く同じ機能は存在しませんが,名前空間の考え方が似ています.パッケージ定義は以下のようにして行います.なお,package 文は,ソースコードの先頭になければなりません.
package パッケージ名;
public class {
	・・・・
}		
  異なるパッケージからは,public 宣言されていないクラス,メソッド,変数を参照できなくなります.また,public 宣言された他のパッケージのクラスやメソッドを参照するには,「パッケージ名.クラス名 or メソッド名」という形で参照できます.ただし,import することによって,クラス名だけによる参照も可能です.

  パッケージも,継承と同様,階層化可能です.ただし,パッケージ名とそのクラスが存在するディレクトリ名は一致している必要があります.例えば,パッケージ「 test.img 」内のクラス img1.class は,ディレクトリ「 test/img 」に存在しなければなりません.

(プログラム例 7.13 ) パッケージ

  下の例では,クラス Sansho (ディレクトリ /temp に存在)から,パッケージ test にあるクラス Complex (ディレクトリ /temp/test に存在)を参照しています.

/****************************/
/* パッケージ               */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

public class Sansho
{
	public static void main (String[] args)
	{

		test.Complex x = new test.Complex (1.0, 2.0);
		test.Complex y = new test.Complex ();

		System.out.print("x = " + "(" + x.real() + "," +
                                  x.imag() + ") abs " + x.v + "\n");
		System.out.print("y = " + "(" + y.real() + "," +
                                  y.imag() + ") abs " + y.v + "\n");
	}
}
--------------------------------------------
/***********************/
/* クラスComplexの定義 */
/***********************/
package test;

public class Complex   // public宣言をしないと,他のパッケージからアクセスできない
					   // (クラス内の変数,メソッドについても同様)
{
	private double r;
	private double i;
	public double v;
	public Complex (double a, double b)   // constructor
	{
		r = a;
		i = b;
		v = Math.sqrt(a * a + b * b);
	}
	public Complex ()   // constructor
	{
		r = 0;
		i = 0;
		v = 0.0;
	}
	public double real() { return r;}
	public double imag() { return i;}
}
		

  クラス参照は,import を使用して,以下のように書くことも可能です.

import java.io.*;
import test.Complex;

public class Sansho
{
	public static void main (String[] args)
	{

		Complex x = new Complex (1.0, 2.0);
		Complex y = new Complex ();

		System.out.print("x = " + "(" + x.real() + "," +
                                  x.imag() + ") abs " + x.v + "\n");
		System.out.print("y = " + "(" + y.real() + "," +
                                  y.imag() + ") abs " + y.v + "\n");
	}
}
		

7.7 いくつかの例

(プログラム例 7.14 ) プラントモデル 

  例えば,化学プラントでは,パイプを通して原料が供給され,それが反応炉等に入り,その成分構成が変化して次の反応炉等へ進むということが繰り返されます.反応炉内の処理やパイプ内の成分構成は変化しても,各反応炉等はパイプを通して入力が入り,パイプに出力されるという点ではほとんど同じです.そこで,様々な反応炉等に対応するクラスを用意しておき,その内部処理をコンストラクタやメソッドで行う(この例の場合は,繰り返しがないため,すべてコンストラクタで行っている)ようにしておけば,それらを適当に組み合わせることによってプラントを実現できるはずです.

  ここでは,
feed  : 原料の供給
add  : 2 つの原料を混ぜる
product: 反応を行う		
の 3 つのクラスを用意し,次のようなプラント(?)をシミュレーションするプログラムを書いてみました.

/****************************/
/* プラントモデル           */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

/********************/
/* クラスFeedの定義 */
/********************/
class Feed {         /* 原料の供給 */
	double x, y, z;
						 // コンストラクタ
	Feed(double x, double y, double z)
	{
		this.x = x;
		this.y = y;
		this.z = z;
	}
						 // 出力
	void print()
	{
		System.out.println("x " + x + " y " + y + " z " + z + " (feed)");
	}
}

/*******************/
/* クラスAddの定義 */
/*******************/
class Add {          /* 混合 */
	double x, y, z;
						 // コンストラクタ
	Add(Feed a, Feed b)
	{
		x = a.x + b.x;
		y = a.y + b.y;
		z = a.z + b.z;
	}
						 // 出力
	void print()
	{
		System.out.println("x " + x + " y " + y + " z " + z + " (add)");
	}
}

/***********************/
/* クラスProductの定義 */
/***********************/
class Product {      /* 製品 */
	double x, y, z;
						 // コンストラクタ
	Product(Add a)
	{
		x = 0.1 * a.x;
		y = 0.2 * a.y;
		z = 0.9 * a.x + 0.8 * a.y;
	}
						 // 出力
	void print()
	{
		System.out.println("x " + x + " y " + y + " z " + z + " (product)");
	}
}

/**************************************/
/* mainメソッドを含むクラスTestの定義 */
/**************************************/
public class Test {
	public static void main(String args[]) throws IOException
	{
		Feed feed1 = new Feed (10.0, 20.0, 0.0);
		feed1.print();

		Feed feed2 = new Feed (5.0, 10.0, 0.0);
		feed2.print();

		Add add1 = new Add (feed1, feed2);
		add1.print();

		Product pro1 = new Product (add1);
		pro1.print();
	}
}
		

  このプログラムの実行により,以下のような結果が得られます.
x 10.0 y 20.0 z 0.0 (feed)
x 5.0 y 10.0 z 0.0 (feed)
x 15.0 y 30.0 z 0.0 (add)
x 1.5 y 6.0 z 37.5 (product)		

(プログラム例 7.15 ) ベクトルの内積と大きさ 

  次は,n 次元ベクトルの内積と大きさを計算するプログラムです.

/****************************/
/* ベクトルの内積と大きさ   */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;

/**********************/
/* Vectorクラスの定義 */
/**********************/
class Vector {
	int n;
	double v[];
	/******************/
	/* コンストラクタ */
	/******************/
	Vector (int n)
	{
		this.n = n;
		v = new double [n];
	}
	/**********/
	/* 大きさ */
	/**********/
	double norm()
	{
		double x = 0.0;
		for (int i1 = 0; i1 < n; i1++)
			x += v[i1] * v[i1];
		return Math.sqrt(x);
	}
	/*********************/
	/* 内積              */
	/*      b : ベクトル */
	/*********************/
	double naiseki(Vector b)
	{
		double x = 0.0;
		for (int i1 = 0; i1 < n; i1++)
			x += v[i1] * b.v[i1];
		return x;
	}
	/********/
	/* 入力 */
	/********/
	void input() throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		for (int i1 = 0; i1 < n; i1++) {
			System.out.print("   " + (i1+1) + "番目の要素は? ");
			v[i1] = Double.parseDouble(in.readLine());
		}
	}
}

/**************************************/
/* mainメソッドを含むクラスTestの定義 */
/*************************************/
public class Test {
	public static void main(String args[]) throws IOException
	{
		Vector a = new Vector (2);
		Vector b = new Vector (2);

		System.out.println("a");
		a.input();
		System.out.println("b");
		b.input();

		System.out.println("a と b の内積 " + a.naiseki(b));
		System.out.println("a,及び,b の大きさ " + a.norm() + " " + b.norm());
	}
}
		

(プログラム例 7.16 ) リスト構造 

  この例は,下図に示すようなリスト構造を生成するプログラムです.データの追加,削除,および,出力の機能を持っています.追加されたデータは,アルファベット順に適当な位置に付け加えられます.ただし,既に存在するデータか否かのチェックは行っていません.

001	/****************************/
002	/* リスト構造               */
003	/*      coded by Y.Suganuma */
004	/****************************/
005	import java.io.*;
006	
007	/********************/
008	/* クラスListの定義 */
009	/********************/
010	class List
011	{
012		String st;   // データ(文字列)
013		List next;   // 次のデータ
014					// コンストラクタ
015		List () { }
016	
017		List (String s)
018		{
019			st  = new String(s);
020		}
021	
022		/**************************************/
023		/* データの追加                       */
024		/*      dt : Listクラスのオブジェクト */
025		/**************************************/
026		void add(List dt)
027		{
028			List lt2 = this;
029			int sw = 1;
030	
031			while (sw > 0) {
032						// 最後に追加
033				if (lt2.next == null) {
034					lt2.next = dt;
035					sw       = 0;
036				}
037						// 比較し,途中に追加
038				else {
039					List lt1 = lt2;
040					lt2      = lt2.next;
041					int k    = dt.st.compareTo(lt2.st);   // 比較
042					if (k < 0) {                     // 追加
043						dt.next  = lt2;
044						lt1.next = dt;
045						sw       = 0;
046					}
047				}
048			}
049		}
050	
051		/*********************/
052		/* データの削除      */
053		/*      st1 : 文字列 */
054		/*********************/
055		void del(String st1)
056		{
057			List lt2 = this;
058			int sw = 1;
059	
060			while (sw > 0) {
061						// データが存在しない場合
062				if (lt2.next == null) {
063					System.out.println("      指定されたデータがありません!");
064					sw = 0;
065				}
066						// 比較し,削除
067				else {
068					List lt1 = lt2;
069					lt2      = lt2.next;
070					int k    = st1.compareTo(lt2.st);   // 比較
071					if (k == 0) {                  // 削除
072						lt1.next = lt2.next;
073						sw       = 0;
074					}
075				}
076			}
077		}
078	
079		/**********************/
080		/* リストデータの出力 */
081		/**********************/
082		void output()
083		{
084			List nt = this.next;
085			while (nt != null) {
086				System.out.println("   data = " + nt.st);
087				nt = nt.next;
088			}
089		}
090	}
091	
092	/**************************************/
093	/* mainメソッドを含むクラスTestの定義 */
094	/**************************************/
095	public class Test
096	{
097		public static void main (String[] args) throws IOException
098		{
099			BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
100			List name = new List ();
101			int sw = 1;
102	
103			while (sw > 0) {
104				String st;
105				System.out.print("1:追加,2:削除,3:出力,0:終了? ");
106				sw = Integer.parseInt(in.readLine());
107				switch (sw) {
108					case 1:   // 追加
109						System.out.print("   データを入力してください ");
110						st = in.readLine();
111						List lt = new List (st);
112						name.add(lt);
113						break;
114					case 2:   // 削除
115						System.out.print("   データを入力してください ");
116						st = in.readLine();
117						name.del(st);
118						break;
119					case 3:   // 出力
120						name.output();
121						break;
122					default :
123						sw = 0;
124						break;
125				}
126			}
127		}
128	}
		
010 行目

  リストを構成するクラス List の定義の始まりであり,090 行目まで続きます.クラス List では,変数として文字列を表す String クラスのオブジェクト(へのポインタ)( 012 行目)とリストを構成する次のオブジェクト(へのポインタ)( 013 行目)を持っています.

015 行目

  引数のないコンストラクタの定義です.初期設定が行われていないため,いずれの変数も null で初期設定されます(文字列データも次のデータも無いことを意味する).

017 行目~ 020 行目

  1 つの引数を持つコンストラクタの定義です.引数として与えられた文字列から String クラスのインスタンスを生成し,変数 st に代入しています( 019 行目).これは,文字列を記憶する領域を確保し,そのアドレスを変数 st に代入することとほぼ等価です.また,次のデータを示す変数 next(次のデータを指すポインタ)に対しては,初期設定が行われていませんので,null に設定されます.

026 行目~ 049 行目

  データを追加するためのメソッドです.028 行目において,lt2 には,最初,リスト構造の最初のオブジェクト(に対するアドレス)が入ります.具体的には,main メソッドにおける base のアドレスです.base は,次のデータに対するアドレスだけを持ちます.lt1 と lt2 を順に変更しながら(039,040 行目),引数として与えられたオブジェクトのデータが lt1 より大きく,かつ,lt2 より小さい場合,その間に与えられたデータを挿入します( 042 行目~ 046 行目).もし,そのような位置が存在しなければ,リスト構造の最後にデータを追加します( 033 行目~ 036 行目).

055 行目~ 077 行目

  データを削除するためのメソッドです.057 行目において,lt2 には,最初,リスト構造の最初のオブジェクト(に対するアドレス)が入ります.lt1 と lt2 を順に変更しながら(068,069 行目),引数として与えられた文字列と等しい文字列を持つオブジェクトが存在すると,そのデータを削除します( 071 行目~ 074 行目).もし,等しいデータが存在しなかった場合は,エラーメッセージを出力します( 062 行目~ 065 行目).

082 行目~ 089 行目

  lt2 に,最初,リスト構造の最初のオブジェクト(に対するアドレス)を代入した後,リスト構造内のすべてのデータを出力します.

109 行目~ 113 行目

  データの追加を要求された場合の処理です.入力されたデータ(文字列)に基づき,新しい List クラスのインスタンスを生成し( 111 行目),それを引数としてメソッド add を呼びます.

123 行目~ 126 行目

  データの削除を要求された場合の処理です.入力されたデータ(文字列)を引数として,メソッド del を呼びます.

  TreeSet クラスを使用すれば,より簡単にプログラムすることができます.

/****************************/
/* リスト構造               */
/*      coded by Y.Suganuma */
/****************************/
import java.io.*;
import java.util.*;

public class Test
{
	public static void main (String[] args) throws IOException
	{
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		TreeSet <String> name = new TreeSet <String> ();
		String st;
		int sw = 1;

		while (sw > 0) {
			System.out.print("1:追加,2:削除,3:出力,0:終了? ");
			sw = Integer.parseInt(in.readLine());
			switch (sw) {
				case 1:   // 追加
					System.out.print("   データを入力してください ");
					st = in.readLine();
					name.add(st);
					break;
				case 2:   // 削除
					System.out.print("   データを入力してください ");
					st = in.readLine();
					name.remove(st);
					break;
				case 3:   // 出力
					Iterator it = name.iterator();
					while (it.hasNext())
						System.out.println("   data = " + it.next());
					break;
				default :
					sw = 0;
					break;
			}
		}
	}
}
		

(プログラム例 7.17 ) ソート(並べ替え) 

  添付したプログラムは,関数名の引き渡し,および,再帰呼び出しを使用して( Java においては,ソート方向を引数で指示,また,再帰呼び出しを使用しない場合もある),5 種類(バブルソート,選択ソート,バケツソート,ヒープソート,および,クイックソート)のソートを行,その実行時間を比較したものです.

(プログラム例 7.18 ) カレンダー 

  添付したプログラムは,カレンダーを出力するためのものです.コンパイルした後,

java Test 年 月 [日] (例: java Test 2011 3 )
実行可能ファイル名 年 月 [日] (例: cal 2011 3 )  // C/C++ の場合

と入力してやれば実行できます.日を入力しない場合は,指定された月のカレンダー(祝日には下線)を,指定した場合は,指定した日の曜日(祝日である場合は,その種類)を出力します.このカレンダーは,1582年以降に対して有効です(ただし,祝日は除く).なお,春分,及び,秋分の日は予測であり,実際の日にちは前年 2 月 1 日付の官報において発表されます.

(プログラム例 7.19 ) 基本アルゴリズム(その1) 

  添付したプログラムは,特に数学的な基本アルゴリズムに関するプログラム例です.最大公約数,素数,行列式,角度の和,平面幾何(三角形の面積,点と直線との距離,二直線の交点,点と線分との関係,二線分の交点の有無,二線分の交点,座標軸の回転),空間幾何(三点を通る平面,点と直線との距離,点と平面との距離,二直線間の最短距離)などを扱っています.

(プログラム例 7.20 ) 基本アルゴリズム(その2)

  データを保存したり,また,保存されたデータの中から特定のデータを探索したりしなければならないような場合が多く存在します.基本アルゴリズム(その2)(ここでは,C/C++ のプログラムを基本とし,Java のプログラムを追加しています)は,線形探索,二分探索,幅優先探索,深さ優先探索,ダイクストラ法( Difkstra 法),動的計画法,文字列探索など,基本的探索方法とデータの保存方法(データ構造)に関するプログラム例です.

演習問題7

:以下の問題において,main メソッド以外では基本的に入出力を行わないこと.

[問1]演習問題 5 の問 1 から問 18 までを(問 6,7,11,12,13,及び,16 を除く),メソッドを利用して書け.

[問2]演習問題 6 の問 1 から問 13 まで(ただし,問 8,9,及び,12 を除く)を,メソッドを利用して書け.

[問3]演習問題 5 の問 22 と問 23 を,メソッドを利用して書け.ただし,f(x) の計算を別のメソッドで行い,f(x) が変化しても,二分法及び台形則による積分を実行するメソッドを変更しなくて済むように書け(メソッド名の引き渡し).

[問4]演習問題 6 の問 16 を,メソッドを利用して書け.

[問5]n 行 m 列の行列の足し算を行うプログラムを,メソッドを利用して書け.なお,メソッドは,任意の n 及び m に対応できるようにせよ.

[問6]キーボードから,「 English This is a pen 」 のように English の後に英文を入力すると,英文を構成する単語数及びすべての文字数を出力するプログラムを main メソッドの引数を利用して書け.

[問7]名前( char ),給与( int ),及び,年齢( int )をクラスで表現し,そこへ入出力を行うプログラムを書け.

[問8] n (入力)個の 3 次元空間の点の座標を入力した後,原点からの平均距離を計算し,平均距離以上離れた点の個数を出力するプログラムを,クラスを使用して書け.

[問9]名前,住所,電話番号をクラスを使用して記述し,名前を入力すると電話番号を出力するプログラムを書け.

[問10]最大待ち行列長を 10 として,待ち行列をクラスで表現するプログラムを書け.ただし,待ち行列は,配列に待ち行列に入っている要素-数字-を格納することによって表すものとする.メソッドとしては,待ち行列に要素を入れるメソッド put(int num),待ち行列から要素を取り出すメソッド get(),及び,待ち行列の状態を出力するメソッド qprint() を用意せよ.

[問11]レコードの格納と取り出しを文字列キーで行うハッシュ表のクラスを書け.その際,レコードの挿入,探索,及び,削除を行うメソッドも用意せよ.

[問12]英語の単語を入力し,それを 2 進木として記憶するプログラムをクラスを利用して書け.ただし,新しい単語が入力されたとき,記憶されている単語よりアルファベット順で前にあるなら左,そうでなければ右の枝をたどるものとする.例えば,11 個の単語が,「 network parameters are optimized from empirical data and the optimal number 」という順に入力された場合は以下のようになる.

情報学部 菅沼ホーム 全体目次 演習解答例 付録 索引