japan.internet.comThe Internet & IT Network
RSS
  • ニュース
  • コラム
  • リサーチ
  • ヘッドライン
  • 特集
  • ブログ
  • プレスリリース
  • 専門チャンネル
  • イベント
  • ランキング
  • ニュースメール
2008年10月14日
文字サイズ文字サイズ小文字サイズ中文字サイズ大
デベロッパー2006年3月14日 10:00

J2SE 5.0のコレクションの使い方を覚える

海外海外internet.com発の記事
  • このエントリーを含むはてなブックマーク
  • この記事をクリップ!
  • Buzzurlにブックマーク
  • Yahoo!ブックマークに登録
  • newsing it!

はじめに

 コレクションAPIは、これまでずっとJava開発キット(JDK)の最も重要な要素の1つでした。ほとんどすべてのJavaプログラムは、HashMapArrayListTreeSetといったコレクションクラスを利用します。これらのクラスはそれぞれ異なる方法でデータを格納するため、たいていのJavaプログラマは、こうしたクラスがどのように動作するかや、どんな場合に利用すべきかをよく理解していました。しかし、J2SE 5.0のリリースによって、コレクションクラスの使い方はすっかり変わってしまいました。

 ありがたいのは、学習曲線が緩やかになったことです。実際、J2SE 5.0のコレクションAPIは、多くの点で簡素化されています。また、J2SE 5.0で採り入れられたさまざまな変更によって、必要なコードの記述量は旧バージョンのJavaよりも少なくなっています。

コレクションに関わるJ2SE 5.0の新機能

 本稿では、コレクションAPIに影響を及ぼす新機能を紹介します。具体的には、次の機能について説明していきます。

  • ジェネリック型
  • 拡張されたforループ
  • オートボクシング

 これらの技術はコレクションAPIに影響を与えています。本稿では、この3つの技術を利用したコレクションクラスのサンプルを紹介します(ソースコードについてはダウンロードサンプルを参照してください)。ここで、新しいコレクションAPIの機能を詳しく説明する前に、ジェネリック型の基本的な部分を解説しておきましょう。

ジェネリック型

 ジェネリック型は、コレクションAPIに追加された最も重要な要素です。拡張されたforループやオートボクシングの機能は、ジェネリック型に強く依存しています。ある程度C++に詳しい方には、Javaのジェネリック型とは「C++のテンプレートに相当するが、多くの点でテンプレートより優れたもの」と説明しておきましょう。ジェネリック型を使うと、コレクションに特定の型を関連付けることができます。J2SE 5.0より前のJavaコレクションは特定の型に関連付けることができず、これが原因で問題がいくつか起こっていました。なぜ問題が起きるのか、次のコードを参照しながら説明します。

import java.util.*;

public class BasicCollection {
  public static void main(String args[]) {
    ArrayList list = new ArrayList();

    list.add( new String("One") );
    list.add( new String("Two") );
    list.add( new String("Three") );

    Iterator itr = list.iterator();
    while( itr.hasNext() ) {
      String str = (String)itr.next();
      System.out.println( str );
    }
  }
}

 これは、J2SE 5.0より前のJavaでよく見られるコレクションクラスのサンプルコードです。このクラスでは、ArrayListを作成し、そこに3つの文字列を追加しています。しかし、このArrayListには特定の型が関連付けられていないことに注意してください。Objectクラスを継承しているクラスでさえあれば、どんなクラスでもArrayListに追加できます。

 次に、ArrayListから文字列を取り出すwhileループに注目してみましょう。

while( itr.hasNext() ) {
   String str = (String)itr.next();
   System.out.println( str );
}

 このループはArrayList内のすべてのアイテムに対して繰り返し処理を行います。ただし、取り出した各要素に対して必ず型変換をしなければなりません。なぜなら、このArrayListは格納すべき型を知らないからです。この問題は、Javaのジェネリック型によって解決されます。ジェネリック型を使えば、このArrayListに特定の型を関連付けることができます。そのため、このBasicCollectionクラスをジェネリック型を使って書き換えれば、さきほどの問題はなくなります。次に示すGenericCollectionクラスは、ジェネリック型を使って書き換えたものです。

import java.util.*;

public class GenericCollection {
  public static void main(String args[]) {
    ArrayList<String> list = new ArrayList<String>();

    list.add( new String("One") );
    list.add( new String("Two") );
    list.add( new String("Three") );
    //list.add( new StringBuffer() );

    Iterator<String> itr = list.iterator();
    while( itr.hasNext() ) {
      String str = itr.next();
      System.out.println( str );
    }
  }
}

 ご覧のとおり、J2SE 5.0用に変更されたこのクラスは、元のコードからほんの数行しか変わっていません。最初の変更箇所は、ArrayListの宣言を行っている行です。J2SE 5.0用のバージョンではString型を用いてArrayListを宣言しています。

   ArrayList<String> list = new ArrayList<String>();

 このコードが、どのようにして型とコレクションの名前を結び付けているかに注意してください。ジェネリック型を指定するには、コレクション名のすぐ後ろに山カッコ(<>)を記述し、その中に型の名前を指定します。

 次に、反復子(イテレータ)の宣言では、String型のためのIteratorを宣言します。このイテレータについても、ArrayListとまったく同じようにしてジェネリック型を指定します。たとえば、次のようになります。

Iterator<String> itr = list.iterator();

 この行は、Iteratorの型をStringに指定しています。これで、この反復子のnextメソッドを呼び出すときには、もう型変換を行わずに済みます。次のように普通にnextメソッドを呼び出すだけで、String型が返ってきます。

String str = itr.next();

 たしかにコードの記述量は減りますが、ジェネリック型は型変換を不要にするだけのものではありません。ジェネリック型を指定しておくと、ArrayListに「サポート外の型」を追加しようとしたときにコンパイルエラーが生成されるのです。では、「サポート外の型」とは何でしょうか。たとえば、今回の例ではArrayListStringを受けとるように宣言したため、String以外のクラスまたはStringのサブクラスがサポート外の型と見なされます。

 サポート外の型の一例として、StringBufferクラスを挙げましょう。今回の例では、ArrayListStringのみを受けとるので、StringBufferオブジェクトを格納することはできません。たとえば、次の行をプログラムに追加するのは誤りです。

list.add( new StringBuffer() );

 ここからがジェネリック型のすばらしいところですが、サポート外の型をArrayListに追加するコードを書いても、ランタイムエラーにはなりません。なぜなら、あらかじめコンパイル時にこの誤りが検出されるからです。この場合は、次のようなコンパイルエラーが発生します。

c:collectionsGenericCollection.java:12: 
   cannot find symbol
symbol  : method add(java.lang.StringBuffer)
location: class java.util.ArrayList<java.lang.String>
    list.add( new StringBuffer() );

 こうしたエラーをコンパイル時に検出できることは、バグのないコードを書こうとする開発者にとって非常に好都合です。J2SE 5.0より前のバージョンであれば、実行時になってClassCastExceptionが発生するまでこのエラーが見つからないことが多かったでしょう。どんな場合も、実行時にバグ発生の適切な条件が揃うのを待つより、コンパイル時にエラーを検出できるほうが好ましいはずです。というのも、この「適切な条件」というのは、導入が終わってこのプログラムをユーザが実行するまで発生しないことが多いからです。

拡張されたforループの利用

 これまでのJavaには、for eachループ構文がありませんでした。Visual BasicやC#などの言語にはこの構文があるので、開発者はコレクションの要素に対するループ処理を容易に行うことができます。for eachループを用いると、反復子を使わずにコレクション内部の要素を参照できます。J2SE 5.0になって(ようやく)Javaにも、従来のforループを拡張する形でfor eachループ構文が用意されました。

 この拡張されたforループは、J2SE 5.0の機能のなかでも最も期待されていたものの1つです。この機能を使えば、さきほど紹介したコードのIteratorがすっかり不要になるため、さらに簡潔なコードが得られます。拡張されたforループを使ってJ2SE 5.0用に変更したコードを以下に示します。

import java.util.*;

public class EnhancedForCollection {
  public static void main(String args[]) {
    ArrayList<String> list = new ArrayList<String>();

    list.add( new String("One") );
    list.add( new String("Two") );
    list.add( new String("Three") );
    //list.add( new StringBuffer() );

    for( String str : list  ) {
      System.out.println( str );
    }
  }
} 

 このコードでは、元のコードからIteratorwhileループが完全に削除され、次の3行になっています。

for( String str : list  ) {
   System.out.println( str );
}

 拡張されたforループの書式は次のとおりです。

for( [collection_item_type] [item_access_variable] : 
   [collection_to_be_iterated] )

 最初のパラメータであるcollection_item_type(コレクション要素の型)は、コレクション作成時に指定したジェネリック型に対応するJavaの型でなければなりません。ここで扱うコレクションは前述のArrayListなので、コレクション要素の型はStringになります。

 2番目のパラメータitem_access_variableは、アクセス変数の名前です。この変数は、コレクションに対するループ処理を繰り返す際に各コレクション要素の値を入れるためのものです。このアクセス変数の型もまた、collection_item_typeで指定したものと同じでなければなりません。

 3番目のパラメータcollection_to_be_iteratedは、繰り返し処理の対象となるコレクションを示します。この変数は、コレクション型として宣言されたものであり、かつcollection_item_typeと一致するジェネリック型を持っていなければなりません。この2つの条件を満たしていない場合は、コンパイルエラーが発生します。

プリミティブデータ型とオートボクシング

 J2SE 5.0に追加されたもう1つの重要な機能が、プリミティブデータ型のボクシングとアンボクシングを自動的に行う機能です。コレクションAPIでプリミティブデータ型の追加と参照を行うコードは複雑になりがちですが、このオートボクシング機能により、コードをかなり簡略化できます。

 ボクシングとアンボクシングについて馴染みがない方は、J2SE 5.0について知る前に、Javaがプリミティブデータ型をどのように扱うかをまず知っておく必要があります。ここでは、本題のJ2SE 5.0に入る前に、コレクションAPIでプリミティブデータ型を利用する簡単なJavaアプリケーションを紹介しましょう。

import java.util.*;

public class PrimitiveCollection {
  public static void main(String args[]) {
    ArrayList list = new ArrayList();

    // box up each integer as they are added
    list.add( new Integer(1) );
    list.add( new Integer(2) );
    list.add( new Integer(3) );
    //list.add( new StringBuffer() );

    // now iterate over the collection and unbox
    Iterator itr = list.iterator();
    while( itr.hasNext() ) {
      Integer iObj = (Integer)itr.next();
      int iPrimitive = iObj.intValue();
      System.out.println( iPrimitive );
   }
  }
}

 上記のコードは、コレクションAPIでプリミティブ型を用いるために必要な2つの手順を示しています。コレクション型は、プリミティブ型ではなくオブジェクト型を格納するため、プリミティブ型の要素を格納したいときには、適切なラッパーオブジェクトにボクシングしなければなりません。たとえば、上記のコードでは、次のようにしてintプリミティブデータ型をIntegerオブジェクトにボクシングしています。

list.add( new Integer(1) );
list.add( new Integer(2) );
list.add( new Integer(3) );

 しかし、このボクシングを行うと、コードの後の部分で、コレクションに格納されたプリミティブ型のint型変数にアクセスしようとするときに問題が起こります。プリミティブデータ型は、Integerオブジェクトとして格納されたため、コレクションから返されるときも、int型の変数ではなくIntegerオブジェクトとして返されます。そのため、ボクシングのときと逆の変換(アンボクシング)が必要になります。

Iterator itr = list.iterator();
while( itr.hasNext() ) {
Integer iObj = (Integer)itr.next();
  int iPrimitive = iObj.intValue();
  System.out.println( iPrimitive );
}

 この例の場合は、まずは各要素をIntegerオブジェクトとして取り出し、iObjとします。次に、intValueメソッドを呼び出すことで、このIntegerオブジェクトをプリミティブ型であるint型に変換します。

 J2SE 5.0のオートボクシングを使えば、コレクションでのプリミティブ型の格納と取り出しがとても容易になります。次に例を示します。

import java.util.*;

public class AutoBoxCollection {
  public static void main(String args[]) {
    ArrayList<Integer> list = new 
      ArrayList<Integer>();

    // box up each integer as it’s added
    list.add( 1 );
    list.add( 2 );
    list.add( 3 );
    //list.add( new StringBuffer() );

    // now iterate over the collection
    for( int iPrimitive: list  ) {
      System.out.println( iPrimitive );
    }
  }
} 

 このオートボクシングのサンプルでは、最初にIntegerのジェネリック型を持つArrayListを作成しています。

ArrayList<Integer> list = new ArrayList<Integer>();

 Integerというラッパー型をジェネリック型とするリストを作成した後は、プリミティブ型であるint型変数を直接このリストに追加できます。

list.add( 1 );
list.add( 2 );
list.add( 3 );

 この場合、それぞれの整数をIntegerオブジェクトでラップする必要はもうありません。さらに、リスト内にある個々の要素の参照もまた容易になります。

for( int iPrimitive: list  ) {
  System.out.println( iPrimitive );
}

 この例は、拡張されたforループを使ってプリミティブデータ型のコレクションの反復処理を行う方法を示しています。ここでは、型変換の必要はなく、プリミティブ型の各変数をint型として直接取り出すことができます。つまり、オートボクシングとアンボクシングによって、プリミティブデータ型をオブジェクトと同じくらい容易にコレクションで扱うことができるのです。

まとめ

 J2SE 5.0にジェネリック型が追加されたことで、コレクションに格納するデータの型を明確に指定できるようになりました。これによって、コレクションが正しい型のオブジェクトだけを受けとることがコンパイラによって保証され、コレクションで扱われる要素の型変換が不要になります。

 また、ジェネリック型により、拡張されたforループの利用が可能になりました。この拡張されたforループは他のプログラミング言語に見られるfor each構文と同じ機能を提供しています。拡張されたforループ構文では、反復子が不要なため、コレクションの各要素に対する繰り返し処理の記述がかなり簡潔になります。

 これまで、コレクションへのプリミティブデータ型の追加は、常に面倒なものでした。int型のようなプリミティブ型をいったんIntegerオブジェクトにボクシングした後、再びint型に戻すためにアンボクシングしなければならなかったのです。オートボクシングとアンボクシングによって、この処理を開発者ではなくJava側に負担させることができ、その結果、プリミティブデータ型を持つコレクションを利用する際のソースコードはずっと読みやすいものになります。

 こうした変更によって、J2SE 5.0のコレクションデータへのアクセス方法は必然的に変化し、コレクションAPIにアクセスするためのJavaコードもずっとシンプルなものになっています。

著者紹介

Jeff Heaton(Jeff Heaton)
ライター、大学教員、コンサルタントとして活動中。4冊の著作があり、論文誌および雑誌で20を超える記事を発表。また、個人のWebサイトを管理し、人工知能とスパイダー/ボットプログラミングをはじめとする話題について情報発信を行っている。メールの宛先はjheaton@heatonresearch.com
関連テーマ
最新トップニュース
Graphic Design Forum
【Graphic Design Forum】
あなたならどうする - 倫理にかかわる問題 (10月14日)
データメーション
【データメーション】
サルにも負けるかも(10月14日)
ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」
【ベンチャー専門家の目利きブログ「なぜこの企業は伸びるのか?」】
「お客様に使われる商品開発は『1%の閃きと99%の努力』から!」/株式会社エス・アンド・ケイ(10月10日)
エンジニアの独り言
【エンジニアの独り言】
得体の知れない情報(?)との向き合い方(9月17日)
最新テクノロジーの意外な処方箋
【最新テクノロジーの意外な処方箋】
昆虫と退屈なことについて(9月16日)
海外のインターネットコムアメリカ韓国ドイツトルコ
Copyright 2008 Jupitermedia Corporation All Rights Reserved.http://www.internet.com/