章のまとめ
- ジェネリックでタイプセーフなコレクション
- 基本的な考え方
プログラムに固定数のオブジェクトのみが含まれており、オブジェクトの有効期間がわかっている場合、それは非常に単純なプログラムです。
通常、プログラムは常に、実行時にのみ判明する特定の条件に基づいて新しいオブジェクトを作成します。それまでは、必要なオブジェクトの数や正確な種類さえ知る方法がありません。この一般的なプログラミングの問題を解決するには、いつでも、どこでも、任意の数のオブジェクトを作成する必要があります。したがって、各オブジェクトを保持するために名前付き参照の作成に依存することはできません。
MyType aReference;
なぜなら、そのような参照が実際にどれだけ必要になるかわからないからです。
ほとんどのプログラミング言語は、この基本的な問題を解決する何らかの方法を提供しています。Java には、オブジェクト (正確には、オブジェクトへの参照) を保存するためのさまざまな方法があります。たとえば、先ほど学習した配列はコンパイラによってサポートされる型です。配列は、オブジェクトのセットを保存する最も効果的な方法です。基本的なタイプのデータのセットを保存する場合は、配列もお勧めします。ただし、配列のサイズは固定されており、より一般的なケースでは、プログラムを作成するときに、必要なオブジェクトの数や、オブジェクトを格納するためのより複雑な方法が必要かどうかがわかりません。そのため、配列の固定サイズ制限は制限が多すぎます。
java.utilライブラリは、この問題を解決するために、かなり完全なコレクション クラスのセットを提供しており、基本的な型はList、Set、Queue、Mapです。これらの型はコンテナ クラスとも呼ばれますが、ここでは Java クラス ライブラリで使用される用語を使用します。コレクションはオブジェクトを保存する高度な方法を提供し、これらのツールを使用して多くの問題を解決できます。
コレクションには他にもいくつかのプロパティがあります。たとえば、Set は値ごとに 1 つのオブジェクトのみを保持し、Mapは一部のオブジェクトを他のオブジェクトに関連付けることができる連想配列です。Java コレクション クラスは、サイズを自動的に調整できます。したがって、配列とは異なり、プログラミング時に、コレクションの大きさを気にせずに、任意の数のオブジェクトをコレクションに配置できます。
Java では直接のキーワード サポートはありませんが、コレクション クラスはプログラミング機能を大幅に強化できる重要なツールです。この章では、Java コレクション クラス ライブラリの基本的な知識を紹介し、いくつかの典型的な使用法に焦点を当てます。ここでは、日常のプログラミングで使用されるコレクションに焦点を当てます。後の「付録: コレクション」トピックでは、残りのコレクションと関連関数、およびそれらの使用方法の詳細についても説明します。
ジェネリックでタイプセーフなコレクション
Java 5 より前のコレクションを使用する場合の大きな問題の 1 つは、コンパイラーによってコレクションに間違った型が挿入される可能性があることです。たとえば、最も基本的で信頼性の高いArrayListを使用したAppleオブジェクトのコレクションを考えてみましょう。さて、 ArrayList は「サイズを自動的に拡張できる配列」と考えることができます。ArrayListの使用は非常に簡単です。インスタンスを作成し、次を使用してオブジェクトを挿入します。ArrayList には、コレクションに含まれる要素の数を示すメソッドもあるので、配列が範囲外であるために誤ってエラーが発生することはありません。add()
get()
size()
この例では、AppleとOrange の両方がコレクションに追加され、その後取り出されます。この例ではジェネリックスが使用されていないため、通常、Java コンパイラは警告を出します。ここでは、警告メッセージを抑制するために特定の注釈が使用されています。注釈は「@」記号で始まり、パラメータを取ることができます。ここでの注釈とそのパラメータは@SuppressWarning
、「未チェック」タイプの警告のみが抑制されることを示しています。
import java.util.*;
class Apple {
private static long counter;
private final long id = counter++;
public long id() {
return id;
}
}
class Orange {
}
public class ApplesAndOrangesWithoutGenerics {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
ArrayList apples = new ArrayList();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
// No problem adding an Orange to apples:
apples.add(new Orange());
for (Object apple : apples) {
((Apple) apple).id();
// Orange is detected only at run time
}
}
}
AppleとOrangeはまったく異なりますが、どちらもObjectであること以外に共通点はありません (クラスがどのクラスを継承するかを明示的に宣言しない場合、自動的にObjectを継承します)。ArrayList はObjectを保存するため、 ArrayListメソッドを通じてAppleadd()
オブジェクトをこのコレクションに入れることができるだけでなく、 Orangeオブジェクトもこのコレクションに入れることができ、コンパイル時や実行時に問題は発生しません。ArrayListメソッドを使用してAppleであると思われるオブジェクトを取り出すと、オブジェクト参照のみが得られるため、これをAppleに変換する必要があります。Appleのメソッドを呼び出す前に、式全体を括弧で囲んで変換を強制する必要があります。そうしないと、構文エラーが発生します。get()
id()
実行時にOrangeオブジェクトをAppleに変換しようとすると、出力に示されているエラーが発生します。
「ジェネリック」の章では、Java ジェネリックを使用したクラスの作成が複雑になる可能性があることを学びます。ただし、事前定義されたジェネリック クラスの使用は非常に簡単です。たとえば、Appleオブジェクトを保持するArrayListを定義するには、ArrayListの代わりにArrayListを使用します。山かっこで囲まれたのは _type パラメータ_ (複数の場合があります) で、このコレクション インスタンスが保存できるタイプを指定します。
ジェネリックスを使用すると、コンパイル時に間違った型のオブジェクトがコレクションに配置されるのを防ぐことができます。以下は同じ例ですが、ジェネリックスを使用しています。
// collections/ApplesAndOrangesWithGenerics.java
import java.util.*;
public class ApplesAndOrangesWithGenerics {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<>();
for (int i = 0; i < 3; i++) {
apples.add(new Apple());
}
// Compile-time error:
// apples.add(new Orange());
for (Apple apple : apples) {
System.out.println(apple.id());
}
}
}
0
1
2
これは、リンゴの定義の右側に表示されますnew ArrayList<>()
。これは「ダイヤモンド構文」と呼ばれることもあります。Java 7 より前では、次のように両端で型宣言が必要でした。
ArrayList<Apple> apples = new ArrayList<Apple>();
型が複雑になるにつれて、この繰り返しによって生成されるコードは非常に混乱し、読みにくくなります。プログラマは、すべての型情報が左側から入手できることを認識するため、コンパイラは右側にそれを強制的に繰り返す必要がありません。型推論は小さな要求でしたが、Java 言語チームはそれを受け入れて改善しました。
ArrayList宣言の型指定により、コンパイラはOrange がapplesに入れられないようにするため、これは実行時エラーではなくコンパイル時エラーになります。
ジェネリックスを使用すると、Listから要素を取得するためにキャストは必要ありません。List は保持している型を知っているため、get()
を呼び出すとキャストを実行します。そのため、ジェネリックを使用すると、コンパイラがコレクションに入れるオブジェクトの型をチェックすることがわかるだけでなく、コレクション内のオブジェクトを操作するときに、より明確な構文も得られます。
型をジェネリック パラメーターとして指定する場合、正確な型のオブジェクトをコレクションに配置することに限定されません。アップキャストは、他の型と同様にジェネリックでも機能します。
// collections/GenericsAndUpcasting.java
import java.util.*;
class GrannySmith extends Apple {
}
class Gala extends Apple {
}
class Fuji extends Apple {
}
class Braeburn extends Apple {
}
public class GenericsAndUpcasting {
public static void main(String[] args) {
ArrayList<Apple> apples = new ArrayList<>();
apples.add(new GrannySmith());
apples.add(new Gala());
apples.add(new Fuji());
apples.add(new Braeburn());
for (Apple apple : apples) {
System.out.println(apple);
}
}
}
したがって、 Appleオブジェクトを保持するように指定されたコレクションにAppleのサブタイプを追加できます。
プログラムの出力はObjectのデフォルトメソッドからtoString()
生成され、クラス名とそれに続くオブジェクトのハッシュ コードの符号なし 16 進表現が出力されます (このハッシュ コードはhashCode()
メソッドによって生成されます)。
基本的な考え方
Java コレクション クラス ライブラリは、「オブジェクトを保持する」という概念を採用し、それを 2 つの異なる概念に分割し、クラス ライブラリの基本インターフェイスとして表現します。
- コレクション: 1 つ以上のルールに従う独立した要素のシーケンス。List は挿入順に要素を保存する必要があり、Set には重複した要素を含めることはできません。Queueはキューイング ルールに従ってオブジェクトが生成される順序 (通常は挿入順序と同じ) を決定します。
- Map : キーを使用して値を検索できるようにする「キーと値」オブジェクトのセット。ArrayList は数値を使用してオブジェクトを検索するため、ある意味、数値とオブジェクトを関連付けます。マップを使用すると、あるオブジェクトを使用して別のオブジェクトを見つけることができます。オブジェクトを他のオブジェクトに関連付けるため、連想配列とも呼ばれます。または、使用できるため、辞書とも呼ばれます。キー オブジェクトは、値オブジェクトを検索するのと同じように、値オブジェクトを検索します。辞書の単語を使った定義。Mapは強力なプログラミング ツールです。
常に可能というわけではありませんが、理想的には、作成するコードのほとんどがこれらのインターフェイスを処理し、使用する正確な型を指定する必要がある唯一の場所は作成時です。したがって、リストは次のように作成できます。
List<Apple> apples = new ArrayList<>();
ArrayList がListにアップキャストされていることに注意してください。これは、前の例で行われたこととまったく逆です。インターフェイスを使用する目的は、特定の実装を変更したい場合に、次のように作成時に変更するだけで済むことです。
List<Apple> apples = new LinkedList<>();
したがって、具象クラスのオブジェクトを作成し、それを対応するインターフェイスにアップキャストして、このインターフェイスをコードの残りの部分で使用する必要があります。
一部の具象クラスには追加機能があるため、このアプローチは常に機能するとは限りません。たとえば、LinkedList にはListインターフェイスに含まれていない追加のメソッドがあり、 TreeMapにもMapインターフェイスに含まれていないメソッドがあります。これらのメソッドを使用する必要がある場合、より一般的なインターフェイスにアップキャストすることはできません。
Collectionインターフェイスは、オブジェクトのコレクションを格納する方法である _sequence_ の概念を一般化します。以下は、Collection (ここではArrayListとして表されます) をIntegerオブジェクトで満たし、コレクション内の各要素を出力する簡単な例です。
// collections/SimpleCollection.java
import java.util.*;
public class SimpleCollection {
public static void main(String[] args) {
Collection<Integer> c = new ArrayList<>();
for (int i = 0; i < 10; i++) {
c.add(i); // Autoboxing
}
for (Integer i : c) {
System.out.print(i + ", ");
}
}
}
0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
この例ではCollectionのメソッド(つまり ) のみを使用するため、 Collectionadd()
を継承する任意のクラスのオブジェクトを使用しても問題なく動作します。ただし、ArrayList は最も基本的なシーケンス型です。
add()
メソッドの名前は、新しい要素をCollectionに追加することを示しています。add()
ただし、ドキュメントには「このCollection に指定された要素が含まれていることを確認してください」と詳しく記載されていますが、これはSetでは要素が存在しない場合にのみ要素が追加されるため、Setの意味が考慮されているためです。ArrayListまたはその他のタイプのListを操作する場合、List は重複要素があるかどうかを気にしないため、常に「それを入れる」ことを意味します。add()
ここに示すように、for-in構文を使用してすべてのコレクションを反復処理することができます。この章の以降の部分では、より柔軟な概念であるiterator についても学習します。