プログラミング応用b 第14回 『コレクション2 (ジェネリッククラス、 Vector<E>, ArrayList<E>)』

今回は,ジェネリッククラスという特殊なクラスの定義方法を紹介し,その後,前回にひきつづいてコレクションクラスのList,Set,Map,Stackについて紹介していきます。

【Vectorクラスの問題点】

前回紹介したVectorクラスは,高機能配列として非常に便利ですが,2点ほど問題があります。
 ・Vectorの問題点その1:データはすべてObject型オブジェクトとして格納するために,int型やdouble型などのプリミティブ型は格納できない。
 ・Vectorの問題点その2:データはすべてObject型オブジェクトとして格納するために,格納したオブジェクトを取り出して利用する際に元の型に変換しなくてはならない。
このことについて,解決策を示します。


【プリミティブラッパークラス】
まず,最初の問題点
 ・Vectorの問題点その1:データはすべてObject型オブジェクトとして格納するために,int型やdouble型などのプリミティブ型は格納できない。
について見てみましょう。実際に以下の様にint型定数をベクタに登録しようとするとエラーになります。

VectorTest2.java

import java.util.Vector;

class VectorTest2 {
	public static void main( String args[ ] ) {
		Vector v = new Vector( );
		v.addElement( 10 ); // ベクタに登録できるのはオブジェクト(の参照値)のみなのでエラーになる
	}
}
解決方法として,プリミティブ型をオブジェクト型としてベクタに登録するという方法があります。各プリミティブ型には下表のように,プリミティブラッパークラスと呼ばれるクラスが用意されています。 これらのクラスは,java.lang パッケージに用意されているので,いちいちインポートする必要はありません。クラス名も,プリミティブ型の型名の頭文字を大文字にしたものなので憶えやすいでしょう(int型に対するInteger型と,charに対応するCharacterクラスだけ,この命名ルールから外れています)。
プリミティブ型 プリミティブラッパークラス
byte Byte
short Short
int Integer
long Long
float Float
double Double
char Character
boolean Boolean


int型に対応するプリミティブラッパークラスのオブジェクトのイメージは下図のようなものです。つまり,プリミティブラッパークラスのオブジェクトは,プリミティブ型のデータを内部に持っていて包んで(ラップ)いるのです。プリミティブラッパークラスのオブジェクトに内包させたい値の設定はコンストラクタで行います値の取り出しは,プリミティブ型の型名を x とすると xValue( ) というメソッドで行うことが出来ます。int型のプリミティブラッパークラスInteger型の場合,値の取り出しは intValue( ) というメソッドで行えるわけです。

実際に,Integer型オブジェクトとして,整数値10をベクタに格納した例を以下に示します。ソースコード右上のアイコンでソースコードをコピーできます。
8行目で, 型キャスト演算子(プログラミング応用b第12回で学習)を使って,ベクタvから取り出したオブジェクト(への参照値)の型を Object から
Integer に変換している事に注意して下さい。
IntegerTest.java

import java.util.Vector;

class VectorTest2 {
	public static void main( String args[ ] ) {
		Vector v = new Vector( );
		v.addElement( new Integer( 10 )  ); // Integer型オブジェクトとしてベクタに登録する。
		
		Integer integer = (Integer) v.elementAt( 0 ); // ベクタから取り出したオブジェクトを Object型から Integer型に戻して,変数 integerに格納する。
		System.out.println( integer.intValue( ) );
	}
}
  

【ジェネリック(総称)クラス】

ベクタの第2の問題点は,
 ・Vectorの問題点その2:データはすべてObject型オブジェクトとして格納するために,格納したオブジェクトを取り出して利用する際に元の型に変換しなくてはならない。
ということです。前掲の IntegerTest.java でも,ベクタに一度登録したオブジェクトは Object型なので,いちいち Integer型に戻してから利用しています。

この問題を解決できる便利な方法が,ジェネリック(総称)クラスと呼ばれる特殊なクラスです(下図)。



端的に言うと,ジェネリッククラスは仮のデータ型名(これを総称型と呼ぶ)を使ってクラスを定義しておき,必要に従って,実際の型を指定するというものです。
使用例を以下に示します。
GenericTest.java

class MyClass< T > { // ジェネリッククラスの定義
	T field;
	MyClass( T t ) {
		field = t;
	}
	T getField( ) { return field; }
}


class GenericTest {
	public static void main( String args[ ] ) {
		// Integer型用の MyClass を使用する。
		MyClass<Integer> mi = new MyClass<Integer>( new Integer( 10 ) );
		System.out.println( mi.getField( ).intValue( ) );

		// Double型用の MyClass を使用する。
		MyClass<Double> md = new MyClass<Double>( new Double( 1.23 ) );
		System.out.println( md.getField( ).doubleValue( ) );
	}
}

実は,Vectorクラスも,ジェネリッククラスのバージョン Vector<E> が用意されています。Vector<E>のEはベクタに格納する要素の型です。
ジェネリッククラスバージョンの Vector<E> を使えば,オブジェクトを元の型のまま(Object型にせずに)格納して,元の型まま取り出すことができます。
前回のベクタの使用例を Vector<E> を使って書き換えた例を以下に示します。
Reverse4.java

import java.io.*;
import java.util.*; // for Vector<E>

public class Reverse4 {
  public static void main( String args[] ) {
    try {
      final int arrayN = 50;
      // ベクタオブジェクトbuffers(初期容量50)
      Vector<String> buffers = new Vector<String>( arrayN ); // String型用のベクタ Vector<String> を使用する。
      String str;
      BufferedReader br = new BufferedReader
                  ( new FileReader( "weapon.csv" ) );
      while( ( str = br.readLine() ) != null ) {
        // ベクタにデータを挿入
        buffers.addElement( str );
      }
      br.close();
      for( int i = buffers.size() - 1; i >= 0 ; i-- )
        System.out.println( buffers.elementAt( i ) );
    }
    catch( IOException ioe ) {
      System.out.println( ioe.toString() );
    }
    catch( ArrayIndexOutOfBoundsException aiobe ) {
      // 添字範囲逸脱の例外を処理
      System.out.println( aiobe.toString() );
    }
  }
}


Vector以外にも便利なコレクションクラスは

ジェネリッククラスとして定義されていますので,

ジェネリッククラスバージョンを使用するようにしましょう。


【ジェネリッククラス定義上の制限】
 この様に,ジェネリッククラスは,データ型に対して汎用的なクラス定義が出来る便利なものですが,定義するときにいくつか細かい制限があります。
今の皆さんの段階で気をつけなければならない代表的な制限は
 ・ 総称型を要素とする配列を生成できない
です(下図(1))。対策としては,下図(2)のように,ジェネリッククラスでは総称型の要素を格納したい配列はObject型の配列とすることです。
なお,そうのような対策をした場合,下図(2)の例にあるように, Object型から総称型(下図の例ではE)への変換を行いますが,その様な処理を
行うメソッドの定義の前に @SuppressWarnings("unchecked") と書かないと警告が出てしまいます。詳しい説明は省きますが,@から始まる
このような記述をアノテーションと呼び,コンパイラに特殊な指示を出す場合などに使われます。


【ArrayList】
 前回も紹介しましたが,Vector型オブジェクトは,複数のスレッドから同時に利用されても安全である(スレッドセーフと言う)ように定義されています。しかし,その分,処理は低速になってしまっています。
Java には,Vectorと同じ様に使える高機能配列として java.util.ArrayList クラスが用意されています。ArrayList はスレッドセーフではない分,高速で,現在では Vector より ArrayList を使う方が主流になっています。
もちろん,ArrayListには Object型を格納する ArrayList と,ジェネリッククラスの java.util.ArrayList<E> が用意されています。

ArrayList, ArrayList<E> の仕様については,こちらを参照して下さい。

以下は,ArrayList <E> の使用例です。


ArrayListSample
import java.util.ArrayList;

class ArrayListSample {
	public static void main( String[ ] args ) {
		
		ArrayList<Integer> ai = new ArrayList<Integer>(); // ArrayList<Integer>型を使用している。
		
		for( int i = 0; i < 10; i++ ) {
			ai.add( new Integer( i ) ); // Integer型要素を追加
		}
		
		for( int i = 0; i < ai.size(); i++ ) { // size()メソッドで有効要素数を得ている点に注意
			System.out.println( ai.get( i ) ); // get()メソッドで指定された要素を取得している。
		}		
		
	}
}

 



【授業内演習】
前回の課題で Vector を使っていた部分を, ArrayList<E> を使って書き換えよ。



戻る