その4: C言語欠点からのJava 配列

Javaの配列

C言語では、実行後してから配列の要素数を変更するこができませんでした。 Cの配列はコンパイル時に決定される、並んだ記憶域の先頭アドレスを意味する仕様だからです。
そして宣言の要素数指定は、定数しかできませんでした。
参考⇒(確認後はブラウザの戻る操作で戻ってください
しかし、Javaでは可能で、以下にJavaの配列宣言と使い方を示します。
 3個の要素を持つ配列の生成し、そのアクセス例です。

public class Test
{
	public static void main(String[] arg)
	{
		int[] a;	//配列を管理(参照)する宣言です。(int a[] の表現でも宣言できます。)

		a = new int[3];//実行時に、int型が3つ並ぶオブジェクトをnew演算子 で生成してaに管理させる。
		//これ以降で、a[0]からa[2]の範囲が使えるようになります。
		
		a[0] = 1;
		a[1] = 2;
		a[2] = 3;
	}
}

上記の宣言で、次のように変数を使って要素数を指定することも可能です。

public class Test
{
	public static void main(String[] arg)
	{
		int n = 3;
		int[] a = new int[n];// 3個の要素を持つ配列です。(C言語ではできませんでした)
		・・・		

上記を、次のC言語ように宣言するとコンパイルエラーになります。

public class Test
{
	public static void main(String[] arg)
	{
		int a[3];	//このようなC言語風の宣言は、次のコンパイルエラーになります。
		
	}
}
式の開始が不正です。
  int a[3] ;
        ^

上記のC言語のように、要素数を指定して配列を作ることはできません。
なぜなら、int 型要素の配列は (int []) 型という変数で管理(参照)できる規則になっているからです。 つまり、配列もオブジェクトの一種で、参照型になっており、 変数は、オブジェクトの管理(参照)用として使う形態なのです。
そしてオブジェクトということは、情報や機能を持つことになります。
オブジェクトの情報を指定する場合は、『 . 』のドット直後のフィールド名で情報を表現します。 配列には、lengthというフィールド名で、要素数の情報を得ることができます。(直接変更はできません)

Java配列の初期化

以下に、初期化を伴う配列宣言とlengthフィールドの使用例を以下に紹介します。

public class Test
{
	public static void func(int []array){
		System.out.println( array.length );	
		//arrayはmainで3個要素として作った配列なので、 3 を表示

		// array[0] から array[2] までアクセスできます。
		// これは、呼び出し側mainのa[0]からa[2]のアクセスです。 
		// 配列要素の変更するとmainのaの配列要素が変更できます。
	}

	public static void main(String[] arg){
		int[] a = new int[] {1 , 2 , 3 };
		// 初期化の配列は、int a[]= {1, 2 , 3 }; の表現も可能です。しかしお勧めしません。

		System.out.println( a.length );	// 3 を表示
		
		func( a );
	}
}

配列は参照型に属するのですが、参照型を引数にすると、その参照内容を関数側で変更できます。
その点では、C言語のポインタを引数にするのと似ています。 しかし配列はオブジェクトなので、配列単体を引数として渡せば、関数側で配列のlengthフィールドより配列の要素数が分かります。 C言語ではこのような芸当ができませんでした。参考⇒(確認後はブラウザの戻る操作で戻ってください

上記new int[] {1 , 2 , 3 }の表現は、配列オブジェクトを生成する時の表現なので、 配列変数の初期化時でなくても使用可能な表現です。
例えば、mainの引数がString []型になっていますが、TestBというクラスのmainメソッドに、 "abc"と"xyz"の配列情報を渡して、mainを呼び出す場合に次のような表現で、可能になります。

TestB.main( new String [] {"abc", "xyz" } );

つまり 使いたい所で、 new 型名[ ] { データ列挙 } を行えばよいのです。

なお、初期化の時に要素数を書けないので注意ください。
new String[10] {"abc", "xyz" }は、コンパイルエラーです。

要素数を書くときには、new String[10] と初期化を伴わない指定にします。
さて、 new 型名[10] で作った配列の各要素は何になるのでしょうか? 型名がint や double であれば必ず0になります。 そして、boolean型要素は false です。 そして、Stringなどの参照型は null 決まっています。

null は何もし参照していない定数として扱うキーワードです。 (C言語の NULL ポインタに相当します。)

C言語配列操作の危険性

C言語では、配列がポインタ型の定数で、ポインタ操作を誤ると、宣言配列以外の記憶空間までアクセスしようとします。 ⇒参考 
また、宣言時配列の要素数をプログラマが管理しなければなりません。

対してJavaでは

配列は、変数を作り、使う前にnew で生成したオブジェクトを管理(参照)させてから使います。
 (参照型の分類で、作る場合はnew で生成しなければ使えません。)
そして、配列オブジェクトのlengthフィールドを使えば、容易に存在しない要素のアクセスを回避できます。
仮に、次のような間違えたコードを書いても、C言語のように「運が悪ければ実行エラーが起きずに変な動作になる」ことはありません。
必ずArrayOutOfBoundsExceptionの例外で、要素範囲を超えたアクセスはできません。

public class Test
{
	public static void func(double []a)
	{
		a[0] = 0.2;
		a[1] = 1.3;
		a[2] = 2.4;	//実行エラー
	}
	public static void main(String[] arg)
	{
		double []a = new double[2];
		func(a);
	}
}

この実行エラーは次のようになります。(例です)

Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: 2
        at Test.func(Test.java:7)
        at Test.main(Test.java:12)

C言語動的配列操作の危険性と、Javaのガベージコレクション

Javaの配列は、必要なタイミングで、自由に生成して利用できます。

public class Test
{
	public static void main(String[] arg){
		int[] a;
		
		// 3個の要素が必要になった。
		a = new int[] {1, 2 , 3 };
		System.out.println( a.length );	// 3 を表示
		
		//配列aを使った処理
		
		// ここで使い終わって、新たに5個の配列が必要になった。	
		a = new int[] {4, 3 , 2, 1 , 0 };// aに新しい5要素の配列を管理させる
		System.out.println( array.length );	//arrayはmainので、 5 を表示
	}
}

このような配列の取り扱いは、C言語のポインタで管理する動的な配列操作に似ています。
 (C言語では、配列の代わりを動的に作ってポインタで管理する方法を行いました。
 参考⇒確認後はブラウザの戻る操作で戻ってください )
 
上記のようにJavaにおけるnew 演算子を伴った配列要素の初期化は、配列変数宣言の所以外でも自由に可能です。 この場合、以前にあった3個の並びの要素記憶域は、どうなるのでしょうか?

 C言語では変数aの管理対象から外れる前に free(a);でその記憶領域を開放しなければなりません。  そうしないでプログラムが動作し続けると、mallocで用意した記憶域が、使用できない記憶空間として残ります。  それが、繰り返されると、実行が続くにつれてメモリリソースが減り、システムが動作できなくなります。  このような現象は、メモリーリークと呼ばれ、そうならないように動的記憶域を管理しなければなりません。

ですがJavaでは、このような管理が不要です。a = new int[] {4, 3 , 2, 1 , 0 }の実行で、 それまで、管理されていた3個分記憶域は、変数管理対象から外れますが、Javaでは、どの変数からも管理されていない記憶域を 定期的に調べて、その記憶域を再び new で使えるように自動管理する機能があるのです。 この機能はガベージコレクション(garbage collection)と呼ばれます。
これが定期的に自動実行するタイミングを、プログラムで知る術はありません。 ですが、プログラムで強制的に実行したい場合もあります。
 その時は、その記憶域を管理している全ての変数にnullを設定し、System.gc()を実行させます。

		a = null;
		System.gc(); //ガベージコレクタを実行