章のまとめ
- for-in とイテレータ
- アダプターメソッドのイディオム
- 章のまとめ
- 簡易集合分類
for-in とイテレータ
これまで、for-in構文は主に配列に使用されてきましたが、任意のCollectionオブジェクトでも機能します。実際にArrayList を操作する際の使用例をいくつか見てきましたが、これがその多用途性の証拠です。
import java.util.*;
public class ForInCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<>();
Collections.addAll(cs,
"Take the long way home".split(" "));
for (String s : cs) {
System.out.print("'" + s + "' ");
}
}
}
csはCollectionであるため、このコードは、for-inの使用がすべてのCollectionオブジェクトの特性であることを示しています。
その理由は、Java 5 でIterableと呼ばれるインターフェイスが導入され、これにはIteratorを生成できるメソッドが含まれているためですiterator()
。for-in は、このIterableインターフェイスを使用してシーケンスを反復処理します。したがって、 Iterableを実装するクラスを作成する場合は、それをfor-inステートメントで使用できます。
import java.util.*;
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how we know the Earth to be banana-shaped.").split(" ");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
@Override
public void remove() {
// Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for (String s : new IterableClass()) {
System.out.print(s + " ");
}
}
}
iterator()
返されるのは、配列内の各単語を反復処理できるIteratorを実装する匿名内部クラスのインスタンスです。main メソッドでは、 IterableClass が実際にfor-inステートメントで使用できることがわかります。
Java 5 では、多くのクラスがIterableであり、主にすべてのCollectionクラスが含まれます (ただし、さまざまなMap は含まれません)。たとえば、次のコードはすべてのオペレーティング システム環境変数を表示します。
import java.util.*;
public class EnvironmentVariables {
public static void main(String[] args) {
for (Map.Entry entry : System.getenv().entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
}
}
}
System.getenv()
Map.Entryの要素で構成されるSetを生成するMapを返します。このSet はIterableであるため、 for-inループで使用できます。entrySet()
for-inステートメントは配列またはその他のIterableに適用されますが、これは配列がIterableでなければならないという意味ではなく、オートボックス化も発生しません。
import java.util.*;
public class ArrayIsNotIterable {
static <T> void test(Iterable<T> ib) {
for (T t : ib) {
System.out.print(t + " ");
}
}
public static void main(String[] args) {
test(Arrays.asList(1, 2, 3));
String[] strings = {
"A", "B", "C"};
// An array works in for-in, but it's not Iterable:
//- test(strings);
// You must explicitly convert it to an Iterable:
test(Arrays.asList(strings));
}
}
配列をIterableパラメーターとして渡そうとすると失敗します。これは、配列からIterableへの自動変換はなく、この変換は手動で実行する必要があることを意味します。
アダプターメソッドのイディオム
Iterableクラスがあり、このクラスを使用する 1 つ以上のメソッドをfor-inステートメントに追加したい場合、どうすればよいでしょうか? たとえば、単語のリストを前方に移動するか後方に移動するかを選択できるようにしたい場合があります。このクラスを直接継承してiterator()
メソッドをオーバーライドする場合は、既存のメソッドを置き換えることのみが可能ですが、走査順序を選択することはできません。
解決策の 1 つは、いわゆるアダプター メソッド イディオムです。for-inステートメントを満たすために特定のインターフェイスを提供する必要があるため、「アダプター」部分は設計パターンから来ています。すでにインターフェイスがあり、別のインターフェイスが必要な場合は、アダプターを作成することでこの問題を解決できます。
ここで、デフォルトの前方反復子を基に逆方向反復子を生成する機能を追加したい場合は、書き換えは使用できず、代わりに for-in 文で使用できる Iterable オブジェクトを生成できるメソッドを追加します。これにより、 for-inステートメントを使用する複数の方法が提供されるようになります。
import java.util.*;
class ReversibleArrayList<T> extends ArrayList<T> {
ReversibleArrayList(Collection<T> c) {
super(c);
}
public Iterable<T> reversed() {
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>() {
int current = size() - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public T next() {
return get(current--);
}
@Override
public void remove() {
// Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class AdapterMethodIdiom {
public static void main(String[] args) {
ReversibleArrayList<String> ral =
new ReversibleArrayList<String>(Arrays.asList("To be or not to be".split(" ")));
// Grabs the ordinary iterator via iterator():
for (String s : ral) {
System.out.print(s + " ");
}
System.out.println();
// Hand it the Iterable of your choice
for (String s : ral.reversed()) {
System.out.print(s + " ");
}
}
}
main メソッドでは、for-inステートメントにralオブジェクトを直接置くと、(デフォルトの) 前方反復子が得られます。ただし、オブジェクトに対してメソッドが呼び出された場合は、動作が異なります。reversed()
この方法を使用すると、IterableClass.javaの例に 2 つのアダプター メソッドを追加できます。
MultiIterableClass.java
import java.util.*;
public class MultiIterableClass extends IterableClass {
public Iterable<String> reversed() {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
int current = words.length - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public String next() {
return words[current--];
}
@Override
public void remove() {
// Not implemented
throw new UnsupportedOperationException();
}
};
}
};
}
public Iterable<String> randomized() {
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
List<String> shuffled =
new ArrayList<String>(Arrays.asList(words));
Collections.shuffle(shuffled, new Random(47));
return shuffled.iterator();
}
};
}
public static void main(String[] args) {
MultiIterableClass mic = new MultiIterableClass();
for (String s : mic.reversed()) {
System.out.print(s + " ");
}
System.out.println();
for (String s : mic.randomized()) {
System.out.print(s + " ");
}
System.out.println();
for (String s : mic) {
System.out.print(s + " ");
}
}
}
IterableClass.java
import java.util.Iterator;
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how we know the Earth to be banana-shaped.").split(" ");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
@Override
public void remove() {
// Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for (String s : new IterableClass()) {
System.out.print(s + " ");
}
}
}
2 番目のメソッドは独自のIteratorrandom()
を作成せず、シャッフルされたList内のIterator を直接返すことに注意してください。
出力からわかるように、Collections.shuffle()
このメソッドは元の配列には影響せず、シャッフルされた 内の参照をスクランブルするだけです。その理由は、このメソッドが の結果をArrayListrandomized()
でラップしているためです。によって生成されたこのList が直接スクランブルされている場合、基になる配列は次のように変更されます。Arrays.asList()
Arrays.asList()
import java.util.*;
public class ModifyingArraysAsList {
public static void main(String[] args) {
Random rand = new Random(47);
Integer[] ia = {
1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
List<Integer> list1 =
new ArrayList<>(Arrays.asList(ia));
System.out.println("Before shuffling: " + list1);
Collections.shuffle(list1, rand);
System.out.println("After shuffling: " + list1);
System.out.println("array: " + Arrays.toString(ia));
List<Integer> list2 = Arrays.asList(ia);
System.out.println("Before shuffling: " + list2);
Collections.shuffle(list2, rand);
System.out.println("After shuffling: " + list2);
System.out.println("array: " + Arrays.toString(ia));
}
}
最初のケースでは、Arrays.asList()
の出力がArrayListのコンストラクターに渡され、iaの要素を参照するArrayListが作成されるため、これらの参照をシャッフルしても配列は変更されません。ただし、Arrays.asList(ia)
の結果が直接使用される場合、この中断によってiaの順序が変更されます。基礎となる配列を物理実装として使用するListArrays.asList()
オブジェクトが生成されることに注意することが重要です。Listオブジェクトに変更を加え、元の配列を変更したくない場合は、別のコレクションにコピーを作成する必要があります。
章のまとめ
Java には、オブジェクトを保存するためのメソッドが多数用意されています。
- 配列は数値インデックスをオブジェクトに関連付けます。適切に型指定されたオブジェクトが格納されるため、オブジェクトを検索するときに結果を型キャストする必要がありません。多次元にすることができ、基本的なタイプのデータを保持できます。配列は実行時に作成できますが、配列のサイズは作成後に変更できません。
- Collection には単一の要素が格納されますが、Map には関連するキーと値のペアが含まれます。Java ジェネリックを使用すると、コレクションに保持されるオブジェクトの型を指定できるため、間違った型のオブジェクトをコレクションに入れることができなくなり、コレクションから要素を取得するときに型キャストする必要がなくなります。コレクションとマップは、要素を追加すると自動的にサイズ変更されます。コレクションはプリミティブ型を保持できませんが、オートボクシング メカニズムは、コレクションに保持されているプリミティブ型とラッパー型の間で双方向変換を実行する役割を果たします。
- 配列と同様に、リストも数値インデックスをオブジェクトに関連付けるため、配列とリストは両方とも順序付けされたコレクションです。
- 大量のランダム アクセスを実行する場合はArrayListを使用し、テーブルの途中で要素を頻繁に挿入または削除する場合はLinkedListを使用します。
- キューとスタックの動作はLinkedListを通じて提供されます。
- マップは、オブジェクト (数字ではなく) をオブジェクトに関連付けるデザインです。HashMapは高速アクセス用に設計されていますが、TreeMap はキーを常にソートし続けるため、 HashMapほど高速ではありません。LinkedHashMap は要素を挿入順に保存しますが、ハッシュを使用して高速アクセス機能を提供します。
- Set は重複した要素を受け入れません。HashSet は最速のクエリ速度を提供しますが、TreeSet は要素を並べ替えられた状態に保ちます。LinkedHashSet は要素を挿入順に保存しますが、高速アクセスを提供するためにハッシュを使用します。
- 新しいコードでは従来のクラスVector、Hashtable、Stackを使用しないでください。
Java コレクション (抽象クラスやレガシー コンポーネントを除く) の簡略化された図を見ると役立ちます。ここには、一般的に使用されるインターフェイスとクラスのみが含まれています。(翻訳者注: 下の画像は元の PDF のスクリーンショットですが、原因不明の原因で問題が発生する可能性があります。翻訳者の図面バージョンはこちらを参照してください)
簡易集合分類
実際には、基本的なコレクション コンポーネントはMap、List、SetおよびQueue の4 つだけであることがわかります。それぞれに 2 つまたは 3 つの実装バージョンがあります ( Queueのjava.util.concurrent実装はこの図には含まれていません)。最も一般的に使用されるセットは、太い黒いワイヤーフレームで表されます。
破線のボックスはインターフェイスを表し、実線のボックスは通常の (具体的な) クラスを表します。白抜き矢印の付いた破線は、特定のクラスがインターフェイスを実装していることを示します。実線の矢印は、クラスが矢印の指すクラスのオブジェクトを生成できることを示します。たとえば、任意のCollection はIteratorを生成でき、List はListIteratorを生成できます( List はCollectionから継承するため、通常のIteratorも生成できます)。
次の例は、さまざまなクラス間のメソッドの違いを示しています。実際のコードはジェネリックの章からのもので、ここでは出力を生成するために呼び出されているだけです。プログラムの出力には、各クラスまたはインターフェイスに実装されたインターフェイスも表示されます。
CollectionDifferences.java
public class CollectionDifferences {
public static void main(String[] args) {
CollectionMethodDifferences.main(args);
}
}
CollectionMethodDifferences.java
import java.lang.reflect.Method;
import java.util.*;
import java.util.stream.Collectors;
public class CollectionMethodDifferences {
static Set<String> methodSet(Class<?> type) {
return Arrays.stream(type.getMethods())
.map(Method::getName)
.collect(Collectors.toCollection(TreeSet::new));
}
static void interfaces(Class<?> type) {
System.out.print("Interfaces in " + type.getSimpleName() + ": ");
System.out.println(Arrays.stream(type.getInterfaces())
.map(Class::getSimpleName)
.collect(Collectors.toList()));
}
static Set<String> object = methodSet(Object.class);
static {
object.add("clone");
}
static void
difference(Class<?> superset, Class<?> subset) {
System.out.print(superset.getSimpleName() +
" extends " + subset.getSimpleName() +
", adds: ");
Set<String> comp = Sets.difference(
methodSet(superset), methodSet(subset));
comp.removeAll(object); // Ignore 'Object' methods
System.out.println(comp);
interfaces(superset);
}
public static void main(String[] args) {
System.out.println("Collection: " +
methodSet(Collection.class));
interfaces(Collection.class);
difference(Set.class, Collection.class);
difference(HashSet.class, Set.class);
difference(LinkedHashSet.class, HashSet.class);
difference(TreeSet.class, Set.class);
difference(List.class, Collection.class);
difference(ArrayList.class, List.class);
difference(LinkedList.class, List.class);
difference(Queue.class, Collection.class);
difference(PriorityQueue.class, Queue.class);
System.out.println("Map: " + methodSet(Map.class));
difference(HashMap.class, Map.class);
difference(LinkedHashMap.class, HashMap.class);
difference(SortedMap.class, Map.class);
difference(TreeMap.class, Map.class);
}
}
Sets.java
import java.util.HashSet;
import java.util.Set;
public class Sets {
public static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<>(a);
result.addAll(b);
return result;
}
public static <T>
Set<T> intersection(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<>(a);
result.retainAll(b);
return result;
}
// Subtract subset from superset:
public static <T> Set<T>
difference(Set<T> superset, Set<T> subset) {
Set<T> result = new HashSet<>(superset);
result.removeAll(subset);
return result;
}
// Reflexive--everything not in the intersection:
public static <T> Set<T> complement(Set<T> a, Set<T> b) {
return difference(union(a, b), intersection(a, b));
}
}
TreeSetを除くすべてのSet は、Collectionとまったく同じインターフェイスを持ちます。ListとCollectionの間には明らかな違いがありますが、 Listで必要なメソッドはすべてCollectionにあります。一方、Queueインターフェースのメソッドは独立しているため、Queue機能を備えた実装を作成する場合はCollectionメソッドを使用する必要はありません。最後に、MapとCollection の間の唯一の共通点は、 Map がentrySet()
およびメソッドを使用してCollection をvalues()
生成できることです。
マーカー インターフェイスjava.util.RandomAccessはArrayListにアタッチされますが、LinkedList にはアタッチされないことに注意してください。これは、特定のListに基づいて動作を動的に変更するアルゴリズムに通知します。
オブジェクト指向の継承階層の観点から見ると、この組織構造は確かに少し奇妙です。ただし、 java.utilのコレクションについて詳しく学ぶと、継承構造が少し奇妙であることに加えて、さらに多くの問題があることがわかります。コレクション ライブラリには常に設計上の課題があり、その解決には、しばしば互いに矛盾する要件を満たす必要があります。したがって、あちこちで妥協する準備をしてください。
これらの問題にもかかわらず、Java コレクションは、プログラムをよりシンプル、より強力、より効率的にするために日常業務で使用される重要なツールです。
以下は、翻訳者が描いた Java コレクション フレームワークの簡略図です。黄色はインターフェイス、緑色は抽象クラス、青色は具象クラスです。破線の矢印は実装関係を表し、実線の矢印は継承関係を表します。