Home >> Advanced Usages on Java Arrays


Javaでの配列の利用


B−1.配列の応用的な利用

■配列そのものをプログラムで取り扱う

☆配列同志の代入

今までは、主に要素について代入を行なってきましたが、配列変数同志で代入を行なうこともできます。配列変数そのものを指定する場合には、角括弧[ ] を指定する必要はありません。
▼配列そのものを参照する書式
 配列名
たとえば、2つの配列originalとaliasがあり、aliasにoriginalの配列の内容を代入するのには、次のように記述します。

int  original [ ]  = { 10, 20, 30, 40, 50 };
int  alias [ ] ;
alias = original ;				//  aliasにoriginalの内容を代入しました
		
このように、配列同志の代入を行ないますと、結果として両方の配列は、同じ配列の実体を共有することになります。例えば、以下の式では配列変数aと配列変数bは同じ配列の実体を指しています。

int  a [ ]  = { 10, 20, 30, 40, 50 };
int  b [ ]  = a;
		
このような場合、プログラマが把握している分には、大丈夫かも知れませんが、次のように別々に代入をし始めると、一体どこで配列の要素が変更されたのかわかりにくくなります。

public class ArrayTest {
	public static void main( String [] arg ) {
		int a [] = { 10, 20, 30 ,40, 50 };
		int b [] = a;
					
		a[ 4 ] = 60;		// 別々に要素に値を代入するが結果的には
		b[ 2 ] = 20;		// 同じ配列に変更を掛けている
		for ( int i=0; iint( "a[ " + i + " ] = " + a[ i ] + "  "  );
		}
		System.out.println( "" );
	}
}
		
この例の場合、結果としては、次のような表示がなされます。

a[ 0 ] = 10	a[ 1 ] = 20	a[  2 ] = 20	a[ 3 ] = 40	a[ 4 ] = 60
		
同じ内容を重複して持っておきたいときは、別々に配列の実体を用意して、次のように繰返しを使って各要素毎に代入をしておく必要があります(注1)。

int a [ ]  = { 10, 20, 30 ,40, 50 };
int b [ ] = new  int[  a.length  ];		// 同じ個数分だけデータ領域を確保しておく

for ( i=0;  i < a.length; i++ ) {   b[ i ] = a[ i ]; }	// 要素を1つずつコピー
		
図9-7 共有とコピーの図 コピーした場合は、別々に値を重複して持っていますので、一方の配列の要素の値を変更しても、それが他方に影響することはありません。 ※注1 配列にはcloneという名前のメソッドが用意されています。これは、コピーを自動的に行なってくれるメソッドですので、繰返しを用いなくても、次のようにしてコピーをつくることができます。

int  b [] = ( int [ ] ) a.clone( );
		

■配列を使った典型的なデータ操作方法

配列を使って、特定の値が入っている要素の番号を求めたり、すべての要素の総和・平均を求めたり、最大値・最小値を求めたり、特定の値を持つ要素の個数をカウントしたりするのは、配列の典型的な使い方と言えます。また、配列の要素を画面に表示したり、グラフとして表示したりすることも良く行なわれます。ここでは、それらの典型的な例を、標準のJavaのプログラムの書き方で、いくつか紹介していきましょう。

■配列の要素の値を表示する

☆すべての要素を表示するアプリケーション

端末画面に配列の要素を表示してみましょう。ただ単に、繰返しとSystem.out.printを使って要素を順番に表示しているだけです。

public class DisplayArrayOnConsole {
	public static void  main(  String [ ]  args ) {
		int	a [  ] =  new int[ 20 ];
		for  (  int   i =0;  i < a.length; i++ ) {
			a[ i ]  = (int)(Math.random( ) *200 );
		}
		for  (  int   i =0;  i < a.length; i++ ) {
			System.out.print( " " + a[ i ]  );
			if ( i % 8 == 7 ) { System.out.println(  ); }
		}
	}
}
		
要素を8個表示するたびに、System.out.printlnメソッドを使って改行するようにしています。前に説明しましたように、繰返しの中で周期的に何かの操作を行ないたいときは、このような剰余演算と条件分岐を用います。

☆すべての要素を表示するアプレット

カレンダー的に数をそのまま表示してみましょう。横に8個ずつ並べます。1つの数を表示するのに、幅30ドット、高さ15ドットを確保しています。y座標は20の位置から表示を始めることとします。

import   java.awt.*;
import   java.applet.*;

public class DisplayArray extends Applet {
	public void  paint(  Graphics  g ) {
		int	a [  ] = { 10, 34, 82, 95, 3, 4 ,5 ,1,32, -32, 1, 3, 12, 45, 11, 32, 19, -6, 23, 21, 33 };
		int	width = 30,  height = 15;
		for  (  int   i =0;  i < a.length; i++ ) {
			g.drawString( " " + a[ i ],  i % 8 * width , i / 8 * height + 20 );
		}
	}
}
		
要素の値を表示するx座標とy座標を技巧的に計算しています。剰余演算はこのように周期的にサイクルさせたいときに使います。また整数除算は、周期毎に1つ値を増やすような場合に使います。次の周期のときには、割り算の結果が1つ大きな値になりますから、表示されるy座標は次の段になります。

☆すべての要素を棒グラフとして表示するアプレット

横のグラフとして表示しています。1つの棒グラフを表示するのに、高さ5ドットを確保し、棒グラフと棒グラフの縦の間隔を10ドットとしています。値の範囲は、0から50までとし、横方向の長さを2倍して、最大100ドットの大きさで表示されるようにしました。

import   java.awt.*;
import   java.applet.*;

public class DisplayGraph extends Applet {
	public void  paint(  Graphics  g ) {
		int	a  [  ] = { 10, 34, 42, 55, 3, 4 ,5 ,1,32, 5, 1, 3, 12, 45, 11 };
		for  (  int   i =0;  i < a.length; i++ ) {
			g.fillRect(  10,  i*10 + 10,  a[ i ] * 2, 5 );
		}
	}
}
		
※横のグラフとして表示しましたが、縦方向にするにはどうしたら良いか考えてください。 図9-8 表示結果

■配列の要素を走査する

☆配列のすべての要素の総和と平均を求めるアプリケーション

以下のプログラムでは、変数sumの値を最初は0にしておいて、各要素の値をsumに足し込んでいます。また、平均は小数点以下の数も表示したいので、sumを一度実数型にして計算結果を実数として表示させています。

public class Summetion {
	public static void  main( String  [ ]  arg ) {
		int	sum, a [  ] =  { 5, 2, 565, 222, 111, 344, 22, 99, 348, 222 };
		sum = 0;
		for ( int  i= 0; i < a.length; i++ ) {
			sum = sum + a[ i ];
		}
		System.out.println( "要素の値の総和は" + sum + "平均は" +
			((double) sum / a.length) + "です。" );
	}
}
		

☆配列のすべての要素の中で、一番値の大きいものを表示する。

次のプログラムは、変数maxに一番値が大きい「要素のインデックス」を保持しています。値そのものを保持していないことに注意してください。アルゴリズムとしては、最初の要素を仮に一番大きい値を保持していると仮定し、maxの値を0にしています。配列を走査していき、それよりも大きな要素が現れたら、それを指すようにしています。走査が終わったら、maxは一番大きい要素のインデックスを示しています。

public class SeekMaximum {
	public  static  void   main(  String  [ ]  args  ) {
		int	max;
		int	a  [ ] = { 5, 2, 565, 222, 111, 344, 22, 99, 348, 222 };

		max = 0;
		for ( int   i= 1; i < a.length ; i++ ) {
			if ( a[ max ] < a[ i ] )  { max = i; }
		}
		System.out.println( "最大の要素は" + max  + "番目で、その値は" 
			+ a [ max ] + "です。" );
	 }
}
		
この方法は、「さらに良い条件の人が現れたら、そちらの人に乗り換える」という打算的な恋愛者のポリシーと同じです。そのような恋愛者は、各時点で、今まで現れた一番良い条件の人とつきあっている筈でしょう(理論的には)。 ※最小値を求めるためにはどうしたらよいか考えてみてください。

☆特定の値が何番目にあるかどうか判定する

あまり効率は良くないのですが、配列の最初から順番に要素を走査して、目的とする値と等しい値を持つ要素のインデックスを求めるプログラムです。変数targetが目的の値を保持しています。同じ値の要素が現れたら、そこで繰返しを抜けています。もし、同じ値の要素が現れなかったら、配列の最後まで走査したことになりますから、繰返しの後で、条件文で最後まで走査したかどうかをチェックしています。そのために、繰返しで配列の走査に用いている変数indexを、for文の前に宣言しています。こうすると、indexはfor文が終了した後も有効になります。

public class SeekTarget {
	public  static  void   main(  String  [ ]  args  ) {
		int	target = 22;
		int	a  [ ] = { 5, 2, 565, 222, 111, 344, 22, 99, 348, 222 };

		int  index = 0;
		for ( ; index < a.length; index++ ) {
			if ( a[ index ] == target )  { break; }
		}
		if  (  index  < a.length  )  {
			System.out.println( target  +  "は" + index + "番目にありました" );
		} else {
			System.out.println( target  +  "はありません!" );
		}
	 }
}
		

☆特定の値が何個あるか数える

変数targetが保持する値と同じ値の要素が幾つあるか数えるためのプログラムです。変数countが個数を保持しています。最初は0個にしておいて、同じ値の要素が現れる度にcountの値を1つずつ増やしていきます。繰返しが終わった段階で、countには最終的な個数が求まっています。

public class ValueCounter {
	public  static  void   main(  String  [ ]  args  ) {
		int	target = 33;
		int	a  [ ] = { 5, 33, 565, 33, 111, 344, 22, 99, 348, 33 };

		int  count = 0;
		for ( int index = 0; index < a.length; index++ ) {
			if ( a[ index ] == target )  { count++; }
		}
		System.out.println( target  +  "は" + count + "個ありました" );
	 }
}
		
※上記プログラムをある値よりも大きい(小さい)要素が何個あるか数えるように、変えてみなさい。

☆要素の順序を入れ替える

乱数を使って、適当な値を要素に代入することは既に説明しましたが、もともと並んでいる要素を適当な順番に入れ替えてみましょう。一度に、2つの要素を入れ替えることはできないので、変数tempを用意して、一旦この変数にコピーして、要素を入れ替えるようにします。2つの要素の選び方は、乱数を使ってインデックスを求めます。ここでは最後に表示させていませんが、配列の要素を順番に表示させると1〜100までの数が適当な順番に並んでいるのがわかります。

public class ElementShifter {
	public  static  void   main(  String  [ ]  args  ) {
		int	a  [ ] = new int[ 100 ];
		for ( int index = 0; index < a.length; index++ ) {  a[ index ] = index + 1; }

		for ( int i = 0; i < a.length * 2; i++ ) {  
			int   index1 = (int)( Math.random( ) * a.length );
			int   index2 = (int)( Math.random( ) * a.length );
			int   temp  =  a[  index1 ];
			a[  index1 ]  = a[  index2 ];
			a[  index2 ]  = temp;
		}
	 }
}
		

■頻度(分布)を求める

☆特定の数の頻度を数える

配列の中にどのような数が入っているか、統計データとして調べたいことがあります。そのときに、ある数がどの程度出現したかを、別の配列に保存するやり方を考えてみましょう。たとえば、整数の配列birthの中に、ある統計調査で調べた誕生月が入っているとします。これを、それぞれの誕生月ごとに何人いるのか頻度を数えてみましょう。整数の配列monthは、それぞれの月について、何人いたかを示します。最初、この配列の要素はすべて0に初期化され(0人であることを示す)、そして、配列birthが走査されるにしたがって、該当する月の要素が+1されていきます。なお、わかりやすくするために、配列monthは、サイズを13にしておき、1〜12までのインデックスの要素だけを使っています。すなわち、month[0]は使われていません。アプリケーションで記述しました。

public class FrequencyCounter {
	public  static  void   main(  String  [ ]  args  ) {
		int	birth [ ] = { 1, 3, 5, 2, 8, 11, 4, 6, 7, 9, 8, 10, 12, 1, 4, 5, 3, 2, 1, 8, 9, 5, 6, 7, 12, 4, 2};
		int	month  [ ] = new int [ 13 ];
		for ( int i = 1; i < month.length; i++ ) {  month[ i ] = 0; }
		
		for ( int index = 0; index < birth.length; index++ ) {
			month[  birth[ index ]  ]++;
		}
		for ( int i = 1; i < month.length; i++ ) {  
			System.out.println( i  +  "月の誕生日の人は" + month[ i ] + "人いました" );
		}
	 }
}
		

☆ある範囲の数の出現の頻度を表示する(度数分布表)

整数のデータが、配列dataに格納されているとします。このデータの範囲が0〜100までとして、10単位に区切り、どれくらいの頻度があるかを調べます。この区切る単位の幅が変数partitionに入っているとします。データの下限はminimum、データの上限はmaximumが指しているとします。頻度は、配列frequentに数えることにします。 アプレットとして、度数分布表として表示してみます。まず、度数を数えた後、区切りを数値で表示し(たとえば、上記の例の最初の範囲だったら、「< 10」という表示になります)、横棒で度数を示し、その値を表示しています。

import   java.awt.*;
import   java.applet.*;

public class FrequencyGraph extends Applet {
	public void  paint(  Graphics  g ) {
		int	data  [  ] = { 10, 34, 42, 55, 33, 0, 64 ,57 ,1, 32, 59, 100, 61, 3, 12, 45, 11, 97 };
		int	partition =10, minimum = 0, maximum = 100;
		int	frequent [ ] = new  int  [   (maximum + partition - minimum)/ partition ];
		for ( int i = 0; i < frequent.length; i++ ) {  frequent[ i ] = 0; }

		for ( int index = 0; index < data.length; index++ ) {
			for ( int i = 0; i <  frequent.length ; i++ ) { 
				if  ( data[ index ]  <  minimum +  partition *  (i+1) )  {
					 frequent[ i ]++;  break; 
				}
			}
		}
		for  (  int   i =0;  i < frequent.length; i++ ) {
			g.drawString(  "< " +  (minimum + partition * (i+1)) , 10,  i*20 + 15 );
			g.fillRect(  50,  i*20 + 10,  frequent[ i ] * 2, 5 );
			g.drawString(  ""+ frequent[ i ] ,  50 + frequent[ i ] * 2 + 5,  i*20 + 15 );
		}
	}
}
		
※partitionや、minimumあるいはmaximumの初期値をいろいろ変えてみて、どのような場合でも動くことを確認しなさい。定められた値の範囲で、配列dataのデータの個数を増やしてみなさい。

■配列とメソッド

メソッドに配列を渡したり、配列を返したりしたい場合があります。その場合には、戻り値の型や、仮パラメータの引数の宣言のところに、配列であることを示す[ ]を記述します。

例:
	void  scanArray(  int  a [ ]  ) { ..... }		// 整数の配列を引数として貰う
	int [ ]  getArray(  ) {  ....... }			// 整数の配列を戻り値として返す
		
メソッドを呼び出す方では、実パラメータの中に配列名を記述します。

int	a [ ] = { 10, 20, 22, 5, 6 };		// ローカルな配列の定義
scanArray(  a  );				// 配列aを引数として渡している
int	b [ ] =  getArray( );			// 戻り値から、配列bを設定している
		

■Java言語上のオブジェクトの配列

オブジェクトを使って関連する情報をまとめあげることは本当に風に役に立つのでしょうか?配列を使った場合を考えてみると、それが顕著になります。たとえば、点の配列を考えてみましょう。オブジェクトを使わなければ、各点のx座標やy座標を別々の配列として持たなければなりませんでした。しかし、Pointクラスのオブジェクトを使えば、オブジェクトの配列を用意すれば、各点を直接表すことができます。x座標やy座標は、配列の要素であるオブジェクトのフィールドを参照すれば求めることができます。

☆オブジェクトの配列の宣言

オブジェクトの配列を宣言するときは、次のように型名の替わりにクラス名を用います。上の方の書式は、オブジェクトの配列を宣言するだけです。下の方の書式は、オブジェクトの配列の実体を用意するものです。
▼オブジェクト配列の宣言の書式
たとえば、次の2つの配列は、それぞれカラーとボタン用の配列となります。

	Color    colors  [ ]  =  new   Color[ 20 ];		//  20個のカラーオブジェクト用の配列
	Button	buttons [  ]  =  new   Button[  10 ];	//  10個のボタンオブジェクト用の配列
		
後は、通常の配列のように用いることができます。配列の要素を指定する場合は、通常のオブジェクトを参照している変数のように用いることができます。
class="program">

	colors[  3  ]  =  new  Color(  10, 30, 40 );		// 3番目の要素にカラーオブジェクトを生成
	g.setColor(  colors[ 3 ] );
	for (  int  i = 0;  i < buttons.length; i++ ) {		// 配列のすべてのボタンに対して
		buttons[ i ]  =  new  Button( "Proceed "+ i );	// 個々の要素にボタンオブジェクトを生成
		buttons[ i ].addActionListener( this );	// アクションリスナーを設定
		add( buttons[ i ] );				//  アプレットの画面上に配置
	}
		
上の例をみてもわかるように、オブジェクトの配列は、オブジェクトそのものを生成したのではありません。new文では配列を1つ生成にしただけに過ぎません。ですから、配列の要素となる個々のオブジェクトは、その都度繰返しなどを使ってnew文を使ってそれぞれを生成していく必要があります。これが、通常の整数などの配列と異なる点です。

☆オブジェクトの配列に対するメソッドの利用の仕方

オブジェクトが配列として用意されているときに、各要素のオブジェクトのメソッド名を使うときは、要素を指定してからドットで区切って、呼び出すメソッド名を指定します。
▼配列の要素のオブジェクトのメソッドを呼び出す書式
 配列名[ インデックスの式 ].メソッド名( 実引数  )

☆オブジェクトの配列を返すメソッドを利用する

オブジェクトの配列を返すようなメソッドを呼び出して利用する場合は、それを受け取るために、オブジェクトの配列用の変数を宣言しておきます。その後は、オブジェクトの配列を使って処理を行います。たとえば、現在使用しているコンピュータで使えるフォントの一覧を得たい場合を考えてみましょう。このときは、java.awtパッケージの中にあるGraphicsEnvironmentクラスのオブジェクトを用います。ただし、このオブジェクトはnew演算子で生成する訳でなく、次のようにして生成し、そのオブジェクトに対してgetAllFontsメソッドを呼び出して、Fontクラスのオブジェクトの配列を得ます。

	GraphicsEnvironment    env = GraphicsEnvironment.getLocalGraphicsEnvironment( );
	Font   allfonts [ ]  = env.getAllFonts( );
		
このようにすれば、後はオブジェクトの配列に対して処理するだけです。たとえば、端末画面にそれらのフォントの名前を全部表示するのには、getNameメソッドを利用します。

for ( int i=0; i < allfonts.length ; i++ ) {
	System.out.println( allfonts[ i ].getName( ) );
}
		

☆オブジェクトの配列の各要素のオブジェクトのインスタンス変数にアクセスする

オブジェクトが配列として用意されているときに、各要素のオブジェクトのインスタンス変数を使うときは、要素を指定してからドットで区切って、インスタンス変数を指定します。
▼配列の要素のオブジェクトのインスタンス変数を参照する書式
 配列名[ インデックスの式 ].インスタンス変数名
たとえば、次のような10個の要素を持つPointクラスのオブジェクトが次のようなプログラムの断片によって、用意されているとしましょう。

Point  points [ ]= new  Point[ 10 ];
for ( int i=0; i< points.length ;  i++ ) {
	points[ i ] = new Point(  (int)(Math.random( )*200) ,  (int)(Math.random( )*100)  );
}
		
この配列の要素であるオブジェクトのインスタンス変数を参照してみましょう。

points[ 0 ].x = 45;
points[ 6 ].x = points[ 3 ].x + 12;
System.out.println( "Fifth point  x-axis: " + points[ 5 ].x + "y-axis:" + points[ 5 ].y );
		
さらに繰返しを使って、各点を線で結んでみましょう(アプレットのpaintメソッドの中で実行されていて、描画領域を仮パラメータ変数gで受け取っているとします)。

for ( int i=0; i< points.length - 1 ;  i++ ) {
	g.drawLine(  points[ i ].x ,  points[ i ].y, points[ i + 1].x,  points[ i + 1 ].y );
}
		
上の繰返しですと、最初(0番目)の点と最後(9番目)の点を結んでくれません。この2つの点を結んで閉じた形にするには、余りの演算を使って次のようにします。どうしてうまくいくかは、考えてみてください。

for ( int i=0; i< points.length;  i++ ) {
	g.drawLine(  points[ i ].x ,  points[ i ].y,
		 points[ (i + 1) % points.length ].x,  points[( i + 1) % points.length ].y );
}
		
図9-1 閉包になるように点を結んだ例

☆オブジェクトの配列の初期化の仕方

オブジェクトの配列の要素をそのインスタンス変数を含めて初期化したいときがあります。このときは、配列と同様に波括弧{ }を使い、コンストラクタも使うことになります。

Point	myPoints  [ ] = { new Point( 33, 22 ),  new Point( 22 , 11 ),  new Point( 55, 19 ) };
		
これは、以下の記述と等価です。注意しなければならないのは、配列をnew演算子で作成した後、更に個々の要素をnew演算子で作成しなければならないことです。配列の生成と個々のオブジェクトの生成は別であるということを再認識しましょう。

Point	myPoints [ ] = new Point [ 3 ];
myPoints[ 0 ] = new Point( 33, 22 );
myPoints[ 1 ] = new Point( 22 , 11 );
myPoints[ 2 ] = new Point( 55, 19 );
		
また、Java 2以降は、配列の宣言と初期値の代入を別々に行なうことができます。これは、配列の章で説明しました。初期値代入をしたい場合は、new演算子を利用することになります。

Point	myPoints;
myPoints = new Point  [ ]  { new Point( 33, 22 ),  new Point( 22 , 11 ),  new Point( 55, 19 ) };
		

☆配列をパラメータで与える場合

配列の場合は、配列の要素をメソッドの中から編集することができます。これは、オブジェクトを引数として与えるときも同じです。引数として与えられたオブジェクトの外部に公開された(publicがつけられた)メソッドやフィールドを呼び出されたメソッドの中で利用することができます。
▼配列を受け取るメソッド定義の書式
 voidあるいは型名あるいはクラス名 メソッド名( 配列の仮引数の宣言, … ){
 }
配列を利用した例を考えてみましょう。例えば、次のメソッドは、論理値を返します。配列と、ある値が引数として与えられ、配列の要素がすべて、引数として与えられた値よりも大きかったら、trueを返します。そうでなければ、falseを返します。要素を調べていって1つでも小さい要素が見つかったらfalseを返して終了するようにしています。繰返しが最後まで終わったら、見つからなかったということでtrueを返しています。

boolean    isAllGreater(  int   a [ ],   int   value  ) {
	for ( int   i=0;   i< a.length; i++ ) {
		if ( a[  i ]   <  value ) {  return   false; }
	}
	return   true;
}
		
これを利用してみましょう。

int   array [ ] = { 18, 20, 40, 50, 30, 40 };
if  (  isAllGreater(  array,  15 ) )  {    ........    }		//  すべての値が15よりも大きかったら
		
次の例は、整数を返すメソッドですが、さきほどと同じように、配列と、ある値が引数として与えられ、もし配列の中にその値が存在したら、最初に見つかった要素のインデックスの値を返します。もしなければ、-1を返します。中身も、先程のメソッドと似ています。

int    scanValue(  int   a [ ],   int   value  ) {
	for ( int   i=0;   i< a.length; i++ ) {
		if ( a[  i ]   ==  value ) {  return   i ; }
	}
	return   -1;
}
		
これを利用してみましょう。

int   array [ ] = { 18, 20, 40, 50, 30, 40 };
int    target =  scanValue( array, 40 );
System.out.println(  "Target Index:" +  target );
		

☆オブジェクト・オブジェクトの配列を返すメソッド

メソッドがオブジェクトを戻り値として返すときには、次のように指定します。自作のクラスのオブジェクトもこの書式を用いて指定することができます。
▼オブジェクトを返すメソッド定義の書式
 クラス名 メソッド名( 仮引数の宣言, … ){
  return オブジェクト;
 }
例えば、前出のPointクラスのオブジェクトを返すようなメソッドは次のように定義します。

Point  sample(   )  {    ......   }		//  メソッドの定義
		
このようなメソッドを利用する呼出し側では、結果のオブジェクトを保持するための変数を用意して、代入します。下の例では変数の宣言も同じに行なっています。

Point   result  =  sample(  );		//  メソッドを呼び出して利用する
		
メソッドが、配列あるいはオブジェクトの配列を、呼出し側に返すときは次のように指定します。
▼基本型あるいはオブジェクトの配列を返すメソッド定義の書式
 型名あるいはクラス名 [ ] メソッド名( 仮引数の宣言, … ){
  return 配列名;
 }
またもや前出のPointクラスのオブジェクトを返すようなメソッドを定義してみます。

Point  [  ]    sampleList(   )  {    ......   }		//  メソッドの定義
		
このメソッドを呼び出す方では、オブジェクトの配列を保持するような変数を用意して、戻り値を代入するようにします。下の例では変数の宣言も同じに行なっています。

Point  resultlist  [  ]   =   sampleList(   );	//  メソッドを呼び出して利用する
		

■2次元配列を使って行列の計算をする

ここでは、正方行列(列数と行数がおなじ)ものを中心に、行列の足し算、引き算、行列の掛け算、行列式を求める計算方法を紹介します。 行列式を求める方法は、ガウスの前進消去と似ていますが、LU分解の方法で求めています。 また、すべて2次元配列を受け取るメソッドの形で記述しています。 まず、呼び出す側では、次のような設定で、乱数で2次元配列が初期化されていると仮定します。 行列計算ですので、当然のことながら、配列の要素は実数型になっています。 以下の記述では、5次の正方行列になっています。 タートル・グラフィックスのライブラリを使うのであれば、以下の記述はstartメソッドの中に記述してください。

		
	int  size = 5;
	double  n[ ][ ] = new double[ size ][ size ];
	double  m[ ][ ] = new double[ size ][ size ];
	double  r[ ][ ] = new double[ size ][ size ];    // 結果用の2次元配列

	double  magnitude = 10;    // 配列の各要素が最大どれくらいの大きさか
	
	// 2つの行列の乱数値による初期化
	for ( int i=0; i < n.length ; i++ ) {
		for ( int j=0; j < n[ i ].length; j++ ) {
			n[ i ][ j ] = Math.random( ) * magnitude;
			m[ i ][ j ] = Math.random( ) * magnitude;
		}
	}
		

もし、行列の値を表示したければ、次のようなメソッドを定義して呼び出してください。 次のメソッドは、1つの2次元配列と次数(正方行列の大きさ、この場合は5です)を引数として受け取ります。 このメソッドは、Java 5から定義された、System.out.printfを使っています。printfは、C/C++言語と似ていますので、 それらの言語を学んだときに思い起こせるのではないでしょうか。この記述は、引数を実数(f)として受け取り、 整数部最大3桁、小数部最大5桁という形で実数をターミナル(文字端末)に表示するように指定しています。 次の行列を表示するときのことを考慮して、最後に再度改行して、空行を1つ用意しています。

		
	void printSquareMatrix( double mat[ ][ ], int size ) {
		for ( int i=0; i < size ; i++ ) {
			for ( int j=0; j < size ; j++ ) {
				System.out.printf( "%3.5f   ", mat[ i ][ j ] );
			}
			System.out.println( );
		}
		System.out.println( );
	}
		

使い方は、至って単純で、上記の乱数で設定された2つの2次元配列を表示する場合は、次のように呼び出します。 タートル・グラフィックスのライブラリを使うのであれば、以下の記述はstartメソッドの中に記述してください。

		
	printSquareMatrix( n, size );
	printSquareMatrix( m, size );
		

☆n次正方行列の加減算

加減算は、簡単なメソッドとして記述できます。正方行列として表現された2次元配列のそれぞれの要素を足したり、 引いたりするだけです。以下は、2つの2次元配列と結果をいれる2次元配列、および次数を受け取り、加減算を行なう メソッドです。2つのメソッドを定義していますが、両者の違いは、ネストされたfor文の中で要素を足しているのか、引いているのかの 違いだけです。


	void addSquareMatrix( double n[ ][ ], double m[ ][ ], double r[ ][ ], int size ) {
		for ( int i=0; i < size ; i++ ) {
			for ( int j=0; j < size; j++ ) {
				r[ i ][ j ] = n[ i ][ j ] + m[ i ][ j ];
			}
		}
	}
	
	void subSquareMatrix( double n[ ][ ], double m[ ][ ], double r[ ][ ], int size ) {
		for ( int i=0; i < size ; i++ ) {
			for ( int j=0; j < size; j++ ) {
				r[ i ][ j ] = n[ i ][ j ] - m[ i ][ j ];
			}
		}
	}
		

それでは、先ほどのnとmとrを使って、呼び出してみましょう。 タートル・グラフィックスのライブラリを使うのであれば、以下の記述はstartメソッドの中に記述してください。


		addSquareMatrix( n, m, r, size );
		printSquareMatrix( r, size );
		subSquareMatrix( n, m, r, size );
		printSquareMatrix( r, size );
		

☆n次正方行列の乗算

行列の積は、ちょっと面倒でした。最初の行列は横方向に、掛け算を行なう次の行列は縦方向に、 各要素を掛け算して、その総和を、結果の行列の対応する要素に入れるものでした。 通常の掛け算と違って、交換則(AB=BA)が成り立たないのも特徴ですし、行列の構造によっては、 乗算が成立しない可能性もありました(最初の行列の列数と、次の行列の行数が等しくなければならない)。 この場合は、正方行列ですから、乗算は成立しますが、いかんせん、積を求めるのは面倒ですね。 以下は、2つの正方行列を2次元配列として受け取り、3つ目の2次元配列に積を求めるものです。 次数も引数として指定しています。


	void multiplySquareMatrix( double n[ ][ ], double m[ ][ ], double r[ ][ ], int size ) {
		for ( int i=0; i < size ; i++ ) {
			for ( int j=0; j < size; j++ ) {
				r[ i ][ j ] = 0;
				for ( int k=0; k < size; k++ ) {
					r[ i ][ j ] = r[ i ][ j ] + n[ i ][ k ] * m[ k ][ j ];
				}
			}
		}
	}
		

上記のように、ネストが1つ深くなっています。5次だと検算するのも嫌ですね。以下は呼び出して、その結果を表示させています。 ここで、合っているかどうか、疑り深い人は、sizeの部分を1とか、2などを記述して、部分的に検算してみては いかがと思います。テストするときも、そのようにして検算しました。

		
	multiplySquareMatrix( n, m, r, size );
	printSquareMatrix( r, size );
		

☆n次正方行列のLU分解と行列式

さあ、ここからが本番で面倒です。受け取った正方行列をLU分解 しています。LU分解って何?っていう人は、取り合えず、正方行列をLという行列とUという行列に分解すると、計算が楽になると思って下さい。 ちなみに、LとUの積を取りますと、元の正方行列に戻ります。さらに、行列Lは対角成分がすべて1の下三角行列、 行列Uは、上三角行列になっていますので、行列Uの対角成分の要素の積を計算すれば、それが行列式の答えになります。 まずは、LUに分解するメソッドを定義します。 実は、LU分解するときには、0で割って発散しないように、注意深く要素を選ばなければなりません。 対角成分の要素をPivot(ピボット)と呼びます。 0や絶対値が0に近い値を持つピボットで割ってはいけないので、行列の行の入れ替えをして、ピボットを一番大きい絶対値を持つ 行の値を使うようにします。


    int  swapcount=0;  // 入れ替えをしたカウント(行列式の計算のために)

	boolean decomposeLU( double n[ ][ ], double L[ ][ ], double U[ ][ ], int size ) {
		// 単位行列IをLに設定し、Uにnのコピーを作ります
		for ( int i=0; i < size; i++ ) {
			for ( int j=0; j < size; j++ ) {
				L[ i ][ j ] = ( i==j ) ? 1.0 : 0.0;
				U[ i ][ j ] = n[ i ][ j ];
			}
		}

		swapcount = 0;
		
		// ピボットで絶対値が一番大きいものを探しだし、行を入れ替えます
		double epsilon = 1.0e-10;
		for ( int i=0; i < size; i++ ) {
			// 絶対値が一番大きいピボットを求めます
			int max = i;
			for ( int j=i+1; j < size; j++ ) {
				if ( Math.abs( U[ max ][ i ] ) < Math.abs( U[ j ][ i ] ) ) {
					max = j;
				}
			}
			/ / もし最大絶対値を持つピボットが0に近ければ、LU分解できないことを告げます
			if ( U[ max ][ i ] >= -epsilon && U[ max ][ i ] <= epsilon ) { return false; }
			
			// 最大絶対値のピボットを持つ行と、i番目の行を入れ替えます
			for ( int j=0; j < size; j++ ) {
				double temp = U[ i ][ j ];
				U[ i ][ j ] = U[ max ][ j ];
				U[ max ][ j ] = temp;
			}
			if ( max != i ) { swapcount++; }
		}

		// ガウスの消去法(掃き出し法)を使って、LとUに分解します
		for ( int k=0; k < size-1; k++ ) {
			for ( int i=k+1; i < size; i++ ) {
				L[ i ][ k ] = U[ i ][ k ]/U[ k ][ k ];
				U[ i ][ k ] = 0;
				for ( int j=k+1; j < size; j++ ) {
					U[ i ][ j ] = U[ i ][ j ] - U[ k ][ j ] * L[ i ][ k ];
				}
			}
		}
		return true;
	}
		

LU分解の仕方ですが、最初は行列Lは、対角成分が1.0である正方行列(一般にはIで表わされる)から始めます。 行列Uは、分解する行列nのコピーで良いでしょう。次の1から9までの数字で構成される3次正方行列をLU分解してみます。 最大絶対値を持つピボットと置き換えるため、次のように、第1行と第3行が入れ替えされています。

これを、各対角成分の値でガウスの消去法(掃き出し法)を使って、 LとUに分解していきます。まず、第1行のピボットの値7と、 第2行の最初の値4を使って、第2行を計算します。次に、第1行のピボットの値7と、第3行の最初の値を1を使って、 第3行を計算します。最後に第2行のピボットの値8と、第3行の2列目の値2を使って、第3行の3列目の値を求めています。

それでは、上記の3次正方行列を分解するサンプルプログラムを記述してみましょう。 まずは、元の行列(N)を表示して、分解されたLとUの行列を表示し、最後にLとUの積を求めています。 元のNと等しいのがわかると思います。


	int  degree=3; // 3×3の正方行列を示します
	double n[ ][ ] = new double [ ] [ ] { {1, 2, 3}, {4, 8, 6}, {7, 5, 9} };
	double L [ ][ ] = new double[ degree ][ degree ];
	double U[ ][ ] = new double[ degree ][ degree ];
	System.out.println( "N:" );
	printSquareMatrix( n, degree );
	if ( decomposeLU( n, L, U, degree ) ) {
		System.out.println( "L:" );
		printSquareMatrix( L, degree );
		System.out.println( "U:" );
		printSquareMatrix( U, degree );
		System.out.println( "L×U:" );
		double r[ ][ ] = new double[ degree ][ degree ];
		multiplySquareMatrix( L, U, r, degree );
		printSquareMatrix( r, degree );
	}
		

行列式を求めるメソッドは、LU分解して、行列Uの対角成分の要素の積を求めているだけです。 ただし、LU分解された際の行の移動の回数が奇数の場合は、マイナスを掛けます。


	double determinant( double n[ ][ ], int size ) {
		if ( size == 1 ) { return n[ 0 ][ 0 ]; }
		double L[ ][ ] = new double[ size ][ size ];
		double U[ ][ ] = new double[ size ][ size ];
		if ( decomposeLU( n, L, U, size ) ) {
			double result = 1.0;
			for ( int i=0; i < size; i++ ) {
				result *= U[ i ][ i ];
			}
			if ( swapcount % 2 != 0 ) { result = -result; }
			return result;
		} else return 0;
	}
		

先ほどの配列について求めるような記述を追加してみてください。値としては、「-54」が求まる筈です。


	double det = determinant( n, degree );
	System.out.println( det );
		

☆n次正方行列の逆行列

LU分解できてしまえば、逆行列は、U-1にL-1を掛けることで求まります。 などと言っても、UやLの逆行列を求めるのも大変です。本当は、LU分解を使って、掃き出し法で求めるのですが、 ここは大学の行列の教科書に従って、行列式と余因子行列の行列式を使って求めてみましょう。 なお、逆行列が存在する必要充分条件は、行列の行列式が0ではないということです。


	void inverse( double n[ ][ ], double r[ ][ ], int size ) {
		double det = determinant( n, size );
		if ( det == 0.0 ) { return; }
		for ( int i=0; i < size; i++ ) {
			for ( int j=0; j < size; j++ ) {
				r[ j ][ i ] = detCofactor( n, i, j, size ) / det;
			}
		}
	}
	
	double detCofactor( double n[ ][ ], int row, int column, int size ) {
		if ( size == 1 ) { return n[ row ][ column ]; }
		double cofactor [ ][ ] = new double[ size-1 ][ size-1 ];
		for ( int i=0, ii=0; i < size; i++, ii++ ) {
			if ( i==row ) { i++; }
			for ( int j=0, jj=0; j < size; j++, jj++ ) {
				if ( j==column ) { j++; }
				cofactor[ ii ][ jj ] = n[ i ][ j ];
			}
		}
		return determinant( cofactor, size-1 )* (((row+column)%2==0)? 1 : -1);
	}
		

最後に検証として、逆行列を求めて、元の行列と乗算をして、単位行列(対角成分だけが1で、他はすべて0)が出てくるかどうか 確かめてみましょう。


	inverse( n, r, size );
	printSquareMatrix( r, size );
	double  ident[ ][ ] = new double[ size ][ size ];
	multiplySquareMatrix( n, r, ident, size );
	printSquareMatrix( ident, size );
		

Bー2.可変長のサイズを持てるオブジェクトの集合用のクラス

配列を使った場合は、配列のサイズは固定でしたので、最初に決まった以上のサイズよりも多くの要素を持つことができませんでした。ユーザやファイルからの入力などを受ける場合など、最初に最大限のサイズがまったくわからない場合も多くあります。そのような場合のために、Listインタフェースがあります。これは、1つ1つの要素を綱で結んだような格好になっており、いくつ要素が増えても大丈夫なようになっています。このような、サイズが未決定の集合(オブジェクトの集合)を扱うための便利なクラスがjava.utilパッケージの中に用意されています。ここでは、集合を表すクラスの中から、頻繁に使われるArrayListクラスについて紹介します。

■ArrayListクラスのオブジェクトで利用可能な主なメソッド

ArrayListは、配列の特徴と、要素をどんどん追加できる両方の特徴を持っています。このため、一番よく使われるリスト構造のクラスになっています。以下に挙げるメソッドは、最初に書いてあるのが、そのメソッドが返す情報の型、あるいはクラスを示しています。括弧の中に書いてあるのは、メソッドに与える実パラメータの型、あるいはオブジェクトのクラスです。仮パラメータの変数名も記述されていますが、実パラメータを与える際には、その型・クラスがあっていればよい形になっています。

int size( )集合の中のオブジェクトの個数を返します
boolean contains( Object element )集合の中に、そのオブジェクトがあるかどうか
Object [ ] toArray( )集合をオブジェクトの配列に変換します
boolean isEmpty( )集合が空かどうか返します
boolean add( Object element )リストの最後に要素を追加する
void add( int index, Object element )指定されたインデックスの後に要素を追加する
Object remove( int index )指定されたインデックスの要素を削除する
Object get( int index )指定されたインデックスの要素を取り出す
Object set( int index, Object element )指定されたインデックスの要素を取り替える
List subList( int fromindex, int toindex )一部分のリストを取り出す
int indexOf( Object target )対象の要素が何番目にあるか返す
int lastIndexOf( Object target )対象の要素で最後に該当するものが何番目か

上記のメソッドの中で、配列の特徴を持つのは、getメソッドとsetメソッドです。配列の要素への参照と代入を示しています。なお、getメソッドで返されたオブジェクトは、Objectクラスのオブジェクトになっていますので、「(目的のクラス名)(オブジェクトを指す変数.get( インデックス ))」という形でクラス変換をする必要があります。addメソッドとremoveメソッドは、リストの特徴を持っています。要素を追加したり、削除したりできます。削除された要素の後に続くすべての要素のインデックスは、1つ前にずれてくれます。

☆ArrayListの使い方の例


import java.util.*;  // ArrayListはjava.utilパッケージに入っています

ArrayList<Integer>  valuelist = new ArrayList<Integer>( );  // Integerクラスの要素を持つArrayListを作る
valuelist.add( new Integer( 123 ) ); // 要素を1つ追加する
int value = linelist.get( 0 ).intValue( );  // 0番目の要素を獲得し、その整数値を変数に代入する
	

☆ArrayListの使い方の応用例

次のアプレットは、ファイルの内容をすべて読み込み、それをボタンがおされるたびに、1行ずつテキストフィールドに表示するアプレットになっています。変数linelistは、個々の要素がファイルの1行の文字列になっているArrayListのオブジェクトを指しています。ファイルから1行ずつ読み込まれ、addメソッドでこのリストに追加されていきます。テキストフィールドには最初の1行が表示されますが、ボタンが押されるたびに行番号(1から始まるようにしています)と共に次の行が表示されていきます。このときはgetメソッドが用いられています。1行をリストから持ってきたら、目的の文字列クラスに型変換しています。なお、一番最後の行まで表示したら、次にボタンが押されたときは、最初の行から表示し直します。

import java.awt.*;
import java.applet.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
import java.util.*;

public class ListReader extends Applet implements ActionListener {
	ArrayList<String>    linelist;   // Java 6.0からは要素のクラス<String>を付ける必要があります
	TextField   result;
	int  current = 0;

	public void init() {
		linelist = new ArrayList<String>();
		try {
			URL   address = new URL( getCodeBase( ), "info" );
			BufferedReader   br = new BufferedReader(
				 new InputStreamReader( address.openStream( ) ) );
			while ( br.ready( ) ) {
				String  line = br.readLine( );
				linelist.add( line );
			}
			br.close( );
		} catch( Exception  exc ) {
			exc.printStackTrace( );
		}
		Button  proceed = new Button( "Go Next" );
		proceed.addActionListener( this );
		add( proceed );
		result =  new TextField(  60  );
		result.setText(  (current+1) + ": " +(String) (linelist.get( current ) ) );
		add( result );
	}

	public void actionPerformed( ActionEvent  ae ) {
		current = (current + 1) % linelist.size( );
		result.setText(  (current+1)+ ": " + (String) (linelist.get( current ) ) );
		repaint( );
	}
}
		

B−3.文字列のその他のメソッドと関連クラス

■文字列の参照・結合と廃棄

☆文字列の参照の変更といらなくなった文字列の廃棄

代入文を用いて、同一の文字列変数に次々と文字列定数を代入していった場合、前に代入されていた文字列は参照されなくなります。

String	fickle = "最初の人" ;
fickle = "次の人" ;
fickle = "更に次の人" ;
		
参照されなくなった文字列定数は、参照されなくなったオブジェクトと同様にいずれガベージコレクションの機構によって、メモリ上から廃棄されることになります。

☆文字列の結合

加算演算子 + が文字列と文字列を結合するのに、あるいは文字列と別の型のデータを結合するのに用いられます。結合された結果は、すべて文字列になります。次の例は、文字列と文字列を足しあわせて文字列変数に代入したものと、文字列と別の型の定数(文字や整数、実数など)を足しあわせて文字列変数に代入したものでうす。最後に文字列変数どうしも足しあわせています。

String	combined = "おせちもいいけど"  + "カレーもね"  ;
String	complicated = "文字と”+  '数' + 30 + "や実数" + 4.5e3 + "を結合" ;
System.out.println(  combined + complicated );
		
文字列変数に対して自己再帰的なの加算演算もできます。以下の例では、変数extendedは毎行異なる文字列を指していくことを注意してください。この例では、結合の計算結果として発生する新しい文字列を参照していくことために、前に代入された文字列は参照されなくなります。

String	extended = "うらら";
extended = extended + "うらら";	// "うららうらら" を新しく参照する
extended += "うらうらら";		// "うららうららうらうらら" を新しく参照する
		
文字列の演算で有効なものは、この加算演算しかありません。減算や乗算などの演算子はありませんし、==や>などの比較演算も、オブジェクトの識別子の比較になってしまうので、文字列の比較にならず、意味を持ちません。文字列の一部を取り出したり、文字列どうしを比較するためには、Stringクラスに用意されているいくつかのメソッドを利用します。

■文字列で使えるメソッド

☆文字列のサイズを知るには?

文字列はStringクラスのオブジェクトですので、メソッドを持つことが可能で、lengthというメソッドが用意されています。このメソッドを使って初期値代入された文字列などのサイズを知ることができます。注意しなければならないのは、配列の場合はフィールドであったので、lengthで良かったのですが、文字列の場合はメソッドなので、length( ) と括弧を付けなければならない点です。
▼サイズを知るための式
文字列.length( )
たとえば、次のように文字列変数に定数を代入して、文字数を得ることができます。最初の方のlengthメソッドの呼出しでは、文字列が17文字から構成されていますので、変数lengthofmakiには17が代入されます。この例では、わかりやすくするために、一旦整数の変数にサイズを代入していますが、直接maki.length( )をその下の行のSystem.out.printlnの引数の中に入れても構いません。また、その下の例のように、文字列定数に対して直接lengthメソッドを呼び出すことができます。この場合は、6という結果が返ってきます。

String    maki= "ああ、やんなっちゃう、ああ、驚いた";
int	lengthofmaki =  maki.length( );
System.out.println(  "文字変数makiの中の文字数は" + lengthofmaki );
System.out.println(  "この文字数は" + "この文字数は".length( ) );
		

☆文字列の比較

オブジェクトに対して、等しいという意味の==と、等しくないという意味の!=を頻繁に用いることができます。しかし、これはオブジェクトの同一性(Identification)をテストするためであって、文字列として等価である(Equivalence)ということを比較することはできません。等価性を確かめたい場合には、比較演算子の替わりに、Stringクラスに用意されている次のようなメソッドを用います。
equals( 文字列 ) 等しいかどうか
equalsIgnoreCase( 文字列 ) 等しいかどうか(大文字小文字の別は無視する)
endsWith( 文字列 ) その文字列で終了するかどうか
startsWith( 文字列 ) その文字列で開始するかどうか
これらのメソッドは、満足する場合は、trueを返してきます。満足しない場合は、falseを返してきます。各メソッドを用いてみましょう。

☆等しいかどうかテストする

次の例は、文字列変数planeに代入されている文字列変数が特定の文字列と等しいかどうかif文を用いてテストしています。

String	plane  =  "F16 Falcon";

if  ( plane.equals( "F16 Falcon"  ) ) { System.out.println( "Air Force Fighter" ); }
else  if  ( plane.equals(  "F18 Hornet"  ) ) {  System.out.println( "Navy Fighter" );  }
else { System.out.println( "Is this a current airplane of US ?" ); }
		
このequalsメソッドは、前の章の音声ファイルの例題のボタンのラベルを変更するアプレットや時間計測のアプレットで、ボタンに設定されたラベルの文字列を比較するために用いられています。

☆大文字小文字の別は無視して、等しいかどうか

equalsIgnoreCaseを利用しますと、文字列中の大文字、小文字の区別は無視してくれます。次の例は、if文の中に現れたこのメソッドのパラメータの中では、比較する文字列は大文字で書かれていますが、文字列変数が保持する小文字混じりの文字列と等しいと判断されます。

String	mytext = "Igor Stranvinsky";

if  ( mytext.equalsIgnoreCase( "IGOR STRAVINSKY" ) ) {
	System.out.println(  mytext + " composed the Fire Bird." );
}
		

☆指定された文字列で開始・終了するかどうか

次の例は、それぞれのif文の中で、文字列が"playing?"で終わるかどうか、"When"で始まるかどうか調べています。最初のif文の条件式は、falseに評価されますし、次の条件式はtrueに評価されます。

String	message = "When did you stop eating?";

if  (  message.endsWith( "playing?" ) ) 			//	false
	{ System.out.println( "Oh, I am just thinking that  I have to  study." ); }
if  (  message.startsWith( "When" ) )			//	true
	{ System .out.println( "I will stop everything before noon" ); }
		

☆辞書順の順序を比較する

等しいことを確かめるのに==演算子が使えないのと同様に、文字列を辞書順に比較するためには、<や>=などの比較演算子は利用することはできません。次のcompareToというメソッドを利用します。
compareTo( 文字列 ) 文字列の辞書順を指定した文字列と比べる
このメソッドは、整数を返してきます。指定した文字列よりも辞書順で先であれば負の数を返してきます。また、等しい文字列なら0を返してきます。辞書順で後になるならば、正の数を返してきます。

String	source = "This is a small message.";

int  result1 =  source.compareTo( "This" );				//	結果は正の数
int  result2 =  source.compareTo( "No, it's long." );			//	結果は正の数
int  result3 =  source.compareTo( "This is a small message." );		//	結果は0
int  result4 =  source.compareTo( "This is not a small message." );	//	結果は負の数
int  result5 =  source.compareTo( "What is the message?" );		//	結果は負の数
		
負の数、正の数が一体どの数になるかは決まっていません。ですから、if文などで用いるときは、次のように0との不等号で判別する必要があります。

String	target = "Rabbit" ;
if ( target.compareTo(  "Lion" )  >  0 )  {	System.out.println( "Lion proceeds the target" ); }
if ( target.compareTo( "Turtle" )  < 0  )  {	System.out.println( "Turtle follows the target" ); }
		

☆位置を指定して一部分を取り出す

文字列の先頭何文字かだけを必要としたり、途中の何文字かだけを必要する場合があると思います。文字列の一部を取り出して、別の文字列として作成して返すメソッドとして、substringメソッドが用意されています。
文字列.substring( 開始位置, 終了直後の位置 )  あるいは
文字列.substring( 開始位置 )
開始位置(0〜length( ) -1の範囲)から、終了位置(0〜length( )の範囲)の手前までの文字列を取り出してくれます。取り出すのが終了直後の位置の「1文字分手前」であるということに注意してください。終了直後の位置を省略すると、開始位置から文字列の最後までを取り出してくれます。もちろん、取り出した後の文字列は別の文字列として生成されますので、元の文字列は変わらないで残されます。 次の例は、3文字目から9文字目を取り出したものと、7文字目以降最後までを取り出したものです。

String   message = "Sample Message";
System.out.println(  message.substring(  3, 10 )  );	// "ple Mes"
System.out.println( message.substring( 7 ) );		// "Message"
		
先頭の5文字分だけ取り出したいとき、あるいは最後の5文字分だけ取り出したいときは、それぞれ次のように指定します。最後の何文字かだけ取り出したいときは、パラメータが1つだけの方のsubstringを使い、lengthメソッドと用いてサイズを求め、そこから文字数分を引くようにして、開始位置を計算しています。

String	firstFive =  message.substring(  0,  5  );			// "Sampl" 最初の5文字
String	lastFive = message.substring(  message.length( )  - 5 );		// "ssage" 最後の5文字
		

☆文字列中の個々の文字を取り出す

文字列では配列と同様に文字列の各文字を参照することができます。位置は配列と同様に0から始まり、文字列のサイズ-1までの範囲で指定することができます。参照には、charAtと呼ばれるメソッドを利用します。このメソッドは、文字列中から指定した位置の文字を返してくれます。
位置を指定して文字を参照するための式
文字列.charAt( 位置の式 )
たとえば、次のようにすれば、それぞれ、先頭の文字、最後の文字を求めることができます。配列のインデックスと同じで、位置は0から始まることに注意してください。

String	crazyCats = "わかっちゃいるけど、やめられない";
char	firstchar = crazyCats.charAt( 0 );			//	'わ'
char	lastchar = crazyCats.charAt( crazyCats.length( ) - 1 );	//	'い'
		
charAtメソッドとsubstringメソッドとの違いに注意してください。substringメソッドでも1文字を取り出すことができますが、取り出した結果は、文字列です。charAtメソッドの場合は、取り出した結果は文字になっています。

String	katochan = "ちょっとだけよ" ;
String	fourth = katochan.substring(  3, 4 );		//  "と"  ←文字列
char	fourthchar = katochan.charAt(  3 );		//  'と'	←文字
		

☆課題10-? 初期値代入されたすべての文字列を1文字ずつ表示する

繰返しとcharAtメソッドを利用すれば、次のようにして、1文字ずつ順番に取り出していくことができます。

String	takostr = " 蛸はどうした?";
for  (  int   i =0;  i < takostr.length( ); i++ ) {
	System.out.println( i + "番目の文字は" + takostr.charAt( i ) + "です。" );
}
		

☆単語がいくつあるか数える

単語と単語の間は1つだけの空白があり、文字列が空白で始まったり、空白で終わったりすることがないという簡単な場合だけを考えてみましょう。変数wordcountで単語の個数を数えます。空白で始まることがありませんから、必ず1語はあるということになります。そして、文字列を先頭から順に見ていき、空白が出てくる度にwordcountの値を1ずつ増やしていきます。文字列の最後まで見終わったときのwordcountの値が単語数になっています。

String	message = "This is a sample message";
int	wordcount = 1;
for ( int   i=0;   i  < message.length( );  i++ ) {
	if  (  message.charAt( i ) == 32  ) {    wordcount++;  }	//空白が現れたら単語数を増やす
}
System.out.println(  message + "の単語の数は" + wordcount );
		
上の例で、if文の中で32と比較しているのは、空白のUnicodeでの文字コードは32だからです。この部分を、次のように空白一個分を開けた文字定数を用いて記述しても構いません。

if  (  message.charAt( i ) == ' '  ) {     wordcount++;  }
		

☆検索のためのメソッド

文字列の検索のためのメソッドが用意されています。それを少しだけ御紹介しましょう。 その文字が何文字目にあるいは、あるいは文字列の場合は、最初の文字が何文字目から始まるか教えてくれます。文字の位置はは0から始まることに注意してください。加えて、同じ文字をそれ以降に探したいときは、つぎの2つのパラメータを持つ同じ名前のメソッドを使います。なお、両方のメソッドとも、探したい文字がない場合は、-1を返してきます。
indexOf( 探したい文字または文字列 ) あるいは
indexOf( 探したい文字または文字列, 探し始める位置 )
実際の例を見てみましょう。次のプログラムの断片は、「桃」という字を2回探しています。

String	message  = "桃も李も桃のうち";
int	search =  message.indexOf(  '桃' );			// 結果は0
int	search2 = message.indexOf( '桃',  search+1 );		// 結果は4
int	search3 =  message.indexOf(  "桃の" );		// 文字列の場合、結果は4
		

■文字列の変更

☆既存の文字列を変更する

これはちょっと面倒です。Stringクラスは、後で文字や文字列を変更するために用意されたクラスではないので、そのままでは既存の文字列を変更することができません。次のようにsubstringと結合演算子(+)を用いれば、一部分を変更することはできますが、これでは新しい文字列を生成してしまいますし、記述も面倒ですし、あまり効果的ではありません。

String	original =  "私は鮪を食べたいのです。" ;
String	modified = original.substring( 0,  2 ) + '鰹' + original.substring( 3 );
			// →"私は鰹を食べたいのです。"という文字列が新しく生成される
		
このように、Stringクラスは、一定の文字列を効率的に処理をするために設計されており、既存の文字列を書き換えることを想定していません。そのため、文字列の個々の文字を頻繁に変更するような場合は、非効率的になります。そのような用途には、StringクラスのサブクラスであるStringBufferクラスを用います。

☆toLowerCase( )およびtoUpperCase( )

文字列のすべての文字を小文字、あるいは大文字に変えてくれます。次の例は、すべて小文字で表示し、その後にすべて大文字で表示します。

String message= "This is a simple message";
System.out.println(  message.toLowerCase( ) );		// "this is a simple message"
System.out.println( message.toUpperCase( ) );		// "THIS IS A SIMPLE MESSAGE"
		

☆trim( )

文字列の先頭や最後にある余分な空白を除去してくれます。

String	whiteSpace = "      Snow in the north field.        ";
System.out.println( whiteSpace.trim(  )   );			//  "Snow in the north field"
		

☆replaceメソッドを使う

Stringクラスには、1文字変更するためにreplaceメソッドが用意されており、これが変更された新しい文字列を返してきてくれます。
String 受け取る変数 = replace( 古い文字, 新しい文字 )
指定した文字をすべて取り替えてくれます。実際の例を見てみましょう。次のプログラムの断片は、「桃」を「蛸」に置き換えた新しい文字列を生成してくれます。

String	message = "桃も李も桃のうち";
String	mymessage = message.replace( '桃', '蛸' );		// "蛸も李も蛸のうち"
		

■書換えのできる文字列(StringBufferクラス)

☆Stringクラスの代わりにStringBufferクラスを用いる

StringBufferクラスは、Stringクラスと異なり、同一のメモリ領域にある文字列に対して操作していくための文字列クラスになっています。StringBufferクラスを用いる場合は、通常のオブジェクトの様に、変数を宣言して、それにオブジェクトを作り、そのパラメータとして文字列を与えてから使うようにします。

StringBuffer	buffer =  new  StringBuffer(  "A sample message" );
		
あとは通常のStringクラスの変数のように用いることができます。ただし、printLineなどにするときは、文字列と足し算するか、キャストを使って(String)に変換しないと使えません。通常は、Stringクラスの文字列が求められているからです。しかし、その不便さを補うかのように、StringBufferクラスのオブジェクトには、文字単位で文字列を変更するためのsetCharAtメソッドやreplaceメソッドが用意されています。以下のように使います。
文字単位で変更する書式
StringBufferクラスの変数.setCharAt( 文字 )
文字の位置は、配列と同様に0〜文字列のサイズ-1の間で指定することができます。このメソッドを用いて変数に代入されている文字列を1文字単位で変更させることができます。文字単位で頻繁に文字の変更がある場合は、StringBufferクラスを用いた方がよいでしょう。StringBufferクラスはStringクラスのサブクラスですので、Stringクラスのオブジェクトで利用できるすべてのメソッドが利用可能になっています。

StringBuffer   takostr = new  StringBuffer( "蛸はどうした?" );
takostr.setCharAt(  0,  '鮪'  );		// "鮪はどうした?" に変更されます
System.out.println(  ""+takostr );
		

☆StringBufferクラスのreplaceメソッドを使う

StringBufferクラスに用意されている同じ名前のメソッドは、3つのパラメータを取りますが、こちらは、指定した範囲にある部分文字列を、指定した文字列で取り替えてくれます。取り替える文字列の長さが、指定した範囲よりも長い場合は、指定した範囲の長さ分だけしか、取り替えてくれません。
 replace( 先頭の位置, 終了直後の位置, 取り替える文字列 )
実際の例を見てみます。次の例は、「さんま」を「たらこ」に取り替えています。

StringBuffer	sentence =  new  StringBuffer(  "目黒のさんまが食べたいなあ" );
sentence.replace(  3,  6,  " たらこ");		//   "目黒のたらこがたべたいなあ"
printLine( ""+sentence );  // あるいは、printLine( new String( sentence) );
		

☆StringBufferクラスの削除、挿入、追加メソッドを使う

特定の位置の文字列を削除したり、特定の位置に、文字列を挿入したりできます。また、文字列の最後に追加できます。 追加は、文字列(Stringクラス)の+演算子の方が楽でしょう。
 delete( 先頭の位置, 終了直後の位置 )
 insert( 先頭の位置, 挿入する文字列 )
 append( 追加する文字列 );

StringBuffer  sentence =  new  StringBuffer(  "This is a simple example of a simple replace testing in a program." );
for ( int scan=0; scan < sentence.length; scan++ ) {
	scan = sentence.indexOf( "a ", scan )
	if ( scan < 0 ) { break; }
	sentence.delete( scan, scan+2 );
	sentence.insert( scan, "the " );
}
printLine( ""+sentence );  // あるいは、printLine( new String( sentence) );
		

■文字列と単語リスト(StringTokenizerクラス)

☆1つの文字列から単語リストに切り出す

1つの文字列を英語の1文と考えて、それを単語リストに分解することを考えましょう。単語は、最大で20個までとします。単語と単語の間は、1個の空白で区切られているとします。次のプログラムでは、まずwordListという文字列を要素とする配列を宣言して、20個分の文字列を参照できるようにしています。文字列sourceに入っている文の単語は、ここに切り出されていきます。単語の数を数えるための変数wordcountを用意しています。文字列sourceの各文字へのインデックスを変数startとendが保持しています。

public class  StringToWord  {
	public   static  void  main(  String  [ ]  args  ) {
		String	source =  "Can you tell me how to get to the airport";
		String	wordList [ ] = new String[ 20 ];

		//  単語リストに分解する
		int  wordcount  = 0;
		int  start,  end;
		for ( start=0; startfor (  end = start;  end < source.length( ) ; end++ ) {
				if ( source.charAt( end ) == 32 ) {  break; }
			}
			wordList[ wordcount ] =  source.substring(  start,  end  );
			wordcount ++ ;
		}
		//  分解した単語リストをすべて表示する
		for  ( int  i = 0 ; i < wordcount ;  i++ )  {
			System.out.println( wordList[  i  ]  );
		}
	}
}
		
一つの単語を取り出すには、startの位置から始めて、endの位置に空白があるかどう内側の繰返しで走査しています。なければ、endの位置を後ろに一つずつずらしていきます。空白が見つかったら、内側の繰返しを抜け出します。そして、startの位置からendの位置の手前までの部分的な文字列をsubstringメソッドで抜き出して、それをwordListに追加しています。外側の繰返しで、次のstart位置はendの次の位置にして、同じことを行なうようにします。最終的に、文字列の最後までこれを繰り返すと単語リストに分解することができます。次の図は、tellという単語を取り出すときのstartとendの値を示しています。 図 tellを取り出すときのstartとend

☆StringTokenizerクラスのオブジェクトを使って分解する

たとえば、ユーザに何かを入力して貰う場合でも1行の1つの情報を入力して貰うような場合もありますが、数値データなどを1行に1レコード分記述したいような場合があります。たとえば、drawLineなどのパラメータで、1つの線の始点と終点の座標値が次のように1行にカンマで区切って入力されているとします。

78, 343, 33, 22
90, 78, 220, 221
10, 100, 150, 200
   :
   		
このような1行に複数の情報が、何らかの区切り用の文字で区切られて保存されている場合には、java.utilパッケージの中に便利なStringTokenizerクラスが用意されています。これは、文字列を区切りの文字列で分解するものです。分解された文字列は、トークン(Token)と呼ばれています。次のようなコンストラクタとメソッドが利用できます。
new StringTokenizer( 文字列, 区切りの文字列 ) // 文字列を区切りの文字列で分解します
String nextToken( ) // 次のトークンを返します
int countTokens( ) // トークンがいくつあるか返します
boolean hasMoreTokens( ) // 次のトークンがあるかどうか返します
なお、StringTokenizerは、java.utilパッケージなので、プログラミングの先頭に次のような一文をいれておく必要があります。
import  java.util.*;
		
なお、区切りの文字列に複数の文字を記述することもできます。各文字をすべて区切りとして認識してくれます。たとえば、次のようなプログラムの断片を記述することができます。これは、スペースで区切られた文字列をトークンに分解して表示するものです。

StringTokenizer  tokens =  new  StringTokenizer( "This is a sample message", "  " );
while ( tokens.hasMoreTokens( )  ) {
	System.out.println( "Token is " +  tokens.nextToken( ) );
}
		
結果は次のように表示されます。

This
is
a
sample
message
		

☆文字列を分解して配列を返すメソッドの例

StringTokenizerクラスを利用して、文字列を受取り、それを区切り文字に従って分解し、分解されたそれぞれの文字列を配列として返すようなメソッドを定義してみましょう。次のプログラムの中のdecomposeというメソッドをみてください。仮パラメータとして受け取った文字列を、空白を区切りとして分解して、その結果を文字列の配列として返しています。呼出し側では、それをresultという名前の文字列配列の変数で受け取り、表示しています。

import    java.util.*;

public class TokenTester {
	public static void main(  String  [  ]   args  )  {
		TokenTester  myself  =  new  TokenTester( );
		String  [  ]   result  =  myself.decompose(  "Hello, my matured example!" );
		for  ( int i = 0; i <  result.length ;  i++ ) {
			System.out.println(  "word:  "  +   result[ i ]  );
		}
	}
	String  [  ]   decompose(   String   source  ) {		//  文字列の配列を返すメソッド
		StringTokenizer  tokens =  new  StringTokenizer(  source, " "  );
		String  [  ]  wordlist =  new  String[  tokens.countTokens(  ) ];
		for ( int i = 0; i <  wordlist.length ;  i++ )  {
			wordlist[ i ] =  tokens.nextToken(  );
		}
		return   wordlist;
	}
}
		

<<Standard Java ⋏ Return to Columns >>Graphics and Curves