章のまとめ
- イテレータ
- リスト反復子
- リンクリスト
- スタックスタック
イテレータ
どのコレクションでも、要素を挿入して再度取得する何らかの方法が必要です。結局のところ、物を保存することはコレクションの最も基本的な仕事です。Listの場合、add()
要素を挿入する方法とget()
要素を取得する方法です。
高レベルの観点から考えると、ここに欠点があることがわかります。コレクションを使用するには、コレクションの正確なタイプをプログラムする必要があります。最初はそれほど悪いことではないように思えるかもしれませんが、次のことを考慮してください。最初にList をコーディングしていたが、後で同じコードをSetに適用できる方が便利だとわかった場合はどうすればよいでしょうか。または、さまざまな種類のコレクションに使用できるように、使用しているコレクションの種類を知らない、または気にしない汎用コードを最初から作成したいとします。書き直すことなく、さまざまな種類のコレクションに適用するにはどうすればよいでしょうか。コードが集まる?
イテレータ(設計パターンでもある) の概念により、この抽象化が可能になります。イテレータは、クライアント プログラマがシーケンスの基礎となる構造を認識したり気にすることなく、シーケンス内を移動してそのシーケンス内の各オブジェクトを選択するオブジェクトです。また、イテレータはしばしば「軽量オブジェクト」 (軽量オブジェクト) と呼ばれます。これは作成コストが安価です。このため、イテレータに奇妙な制約が見られることがよくあります。たとえば、Java のIterator は一方向にのみ移動できます。このイテレータは次の場合にのみ使用できます。
- このメソッドを使用して
iterator()
、コレクションにIteratorを返すように要求します。イテレータはシーケンス内の最初の要素を返す準備ができています。 - メソッドを使用して、
next()
シーケンス内の次の要素を取得します。 - このメソッドを使用して、
hasNext()
シーケンス内にまだ要素が存在するかどうかを確認します。 - このメソッドを使用して、
remove()
イテレータによって最後に返された要素を削除します。
どのように機能するかを確認するには、ここでも「タイプ情報」の章のPetツールを使用します。
SimpleIteration.java
import java.util.Iterator;
import java.util.List;
public class SimpleIteration {
public static void main(String[] args) {
List<Pet> pets = new PetCreator().list(12);
Iterator<Pet> it = pets.iterator();
while (it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// A simpler approach, when possible:
for (Pet p : pets) {
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
// An Iterator can also remove elements:
it = pets.iterator();
for (int i = 0; i < 6; i++) {
it.next();
it.remove();
}
System.out.println(pets);
}
}
その他の関連カテゴリ:
Cat.java
public class Cat extends Pet {
public Cat(String name) {
super(name); }
public Cat() {
super(); }
}
Creator.java
import java.lang.reflect.InvocationTargetException;
import java.util.*;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public abstract class Creator implements Supplier<Pet> {
private Random rand = new Random(47);
// The different types of Pet to create:
public abstract List<Class<? extends Pet>> types();
@Override
public Pet get() {
// Create one random Pet
int n = rand.nextInt(types().size());
try {
return types().get(n)
.getConstructor().newInstance();
} catch (InstantiationException |
NoSuchMethodException |
InvocationTargetException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
public Stream<Pet> stream() {
return Stream.generate(this);
}
public Pet[] array(int size) {
return stream().limit(size).toArray(Pet[]::new);
}
public List<Pet> list(int size) {
return stream().limit(size).collect(Collectors.toCollection(ArrayList::new));
}
}
キムリック.java
public class Cymric extends Manx {
public Cymric(String name) {
super(name); }
public Cymric() {
super(); }
}
犬.java
public class Dog extends Pet {
public Dog(String name) {
super(name); }
public Dog() {
super(); }
}
エジプシャンマウ.java
public class EgyptianMau extends Cat {
public EgyptianMau(String name) {
super(name); }
public EgyptianMau() {
super(); }
}
ハムスター.java
public class Hamster extends Rodent {
public Hamster(String name) {
super(name); }
public Hamster() {
super(); }
}
個人.java
import java.util.Objects;
public class Individual implements Comparable<Individual> {
private static long counter = 0;
private final long id = counter++;
private String name;
public Individual(String name) {
this.name = name;
}
// 'name' is optional:
public Individual() {
}
@Override
public String toString() {
return getClass().getSimpleName() +
(name == null ? "" : " " + name);
}
public long id() {
return id;
}
@Override
public boolean equals(Object o) {
return o instanceof Individual &&
Objects.equals(id, ((Individual) o).id);
}
@Override
public int hashCode() {
return Objects.hash(name, id);
}
@Override
public int compareTo(Individual arg) {
// Compare by class name first:
String first = getClass().getSimpleName();
String argFirst = arg.getClass().getSimpleName();
int firstCompare = first.compareTo(argFirst);
if (firstCompare != 0) {
return firstCompare;
}
if (name != null && arg.name != null) {
int secondCompare = name.compareTo(arg.name);
if (secondCompare != 0) {
return secondCompare;
}
}
return (arg.id < id ? -1 : (arg.id == id ? 0 : 1));
}
}
マンクス.java
public class Manx extends Cat {
public Manx(String name) {
super(name); }
public Manx() {
super(); }
}
マウス.java
public class Mouse extends Rodent {
public Mouse(String name) {
super(name); }
public Mouse() {
super(); }
}
Mutt.java
public class Mutt extends Dog {
public Mutt(String name) {
super(name); }
public Mutt() {
super(); }
}
ペット.java
public class Pet extends Individual {
public Pet(String name) {
super(name);
}
public Pet() {
super();
}
}
ペットクリエイター.java
import java.util.*;
public class PetCreator extends Creator {
// No try block needed.
public static final
List<Class<? extends Pet>> ALL_TYPES = Collections.unmodifiableList(Arrays.asList(
Pet.class, Dog.class, Cat.class, Rodent.class,
Mutt.class, Pug.class, EgyptianMau.class,
Manx.class, Cymric.class, Rat.class,
Mouse.class, Hamster.class));
// Types for random creation:
private static final
List<Class<? extends Pet>> TYPES =
ALL_TYPES.subList(
ALL_TYPES.indexOf(Mutt.class),
ALL_TYPES.size());
@Override
public List<Class<? extends Pet>> types() {
return TYPES;
}
public static void main(String[] args) {
System.out.println(TYPES);
List<Pet> pets = new PetCreator().list(7);
System.out.println(pets);
}
}
/* Output:
[class reflection.pets.Mutt, class reflection.pets.Pug,
class reflection.pets.EgyptianMau, class
reflection.pets.Manx, class reflection.pets.Cymric, class
reflection.pets.Rat, class reflection.pets.Mouse, class
reflection.pets.Hamster]
[Rat, Manx, Cymric, Mutt, Pug, Cymric, Pug]
*/
パグ.java
public class Pug extends Dog {
public Pug(String name) {
super(name); }
public Pug() {
super(); }
}
ラット.java
public class Rat extends Rodent {
public Rat(String name) {
super(name); }
public Rat() {
super(); }
}
げっ歯類.java
public class Rodent extends Pet {
public Rodent(String name) {
super(name); }
public Rodent() {
super(); }
}
印刷内容は以下の通りです。
Iteratorを使用すると、コレクション内の要素の数を心配する必要がなくなります。これは気になることhasNext()
ですnext()
。
Listオブジェクト自体を変更するつもりがなく、List を前方に移動するだけの場合は、 for-in構文を使用する方がより簡潔です。
Iterator は、によって生成された最後の要素を削除することもできますnext()
。つまり、 を呼び出すremove()
前に Iterator を呼び出す必要がありますnext()
。
コレクション内のすべてのオブジェクトに対して操作を実行するというアイデアは強力であり、本書全体で使用されています。
ここで、display()
コレクションの正確な型を知る必要がないメソッドを作成することを検討してください。
public class CrossCollectionIteration {
public static void display(Iterator<Pet> it) {
while (it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Pet> pets = new PetCreator().list(8);
LinkedList<Pet> petsLL = new LinkedList<>(pets);
HashSet<Pet> petsHS = new HashSet<>(pets);
TreeSet<Pet> petsTS = new TreeSet<>(pets);
display(pets.iterator());
display(petsLL.iterator());
display(petsHS.iterator());
display(petsTS.iterator());
}
}
display()
このメソッドには、通過するシーケンスに関する型情報は含まれません。これは、Iteratorsの真の力、つまりシーケンスをトラバースする操作をシーケンスの基礎となる構造から切り離す機能を示しています。このため、イテレータはコレクションへのアクセスを統合すると言うことがあります。
Iterableインターフェイスを使用して、前の例のより簡潔なバージョンを生成できます。これは、「イテレータを生成できるもの」を記述します。
import java.util.*;
public class CrossCollectionIteration2 {
public static void display(Iterable<Pet> ip) {
Iterator<Pet> it = ip.iterator();
while (it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ":" + p + " ");
}
System.out.println();
}
public static void main(String[] args) {
List<Pet> pets = new PetCreator().list(8);
LinkedList<Pet> petsLL = new LinkedList<>(pets);
HashSet<Pet> petsHS = new HashSet<>(pets);
TreeSet<Pet> petsTS = new TreeSet<>(pets);
display(pets);
display(petsLL);
display(petsHS);
display(petsTS);
}
}
ここのクラスはすべてIterableなので、display()
呼び出しが明らかに簡単になりました。
リスト反復子
ListIterator は、さまざまなListクラスによってのみ生成できるIteratorのより強力なサブタイプです。Iterator は前方にのみ移動できますが、ListIterator は両方向に移動できます。このメソッドは、反復子が指すリスト内の次および前の要素のインデックスを生成し、メソッドを使用してアクセスした最新の要素を置き換えることができます。メソッドを呼び出すことによって、 Listの先頭を指すListIterator を生成できます。また、メソッドを呼び出すことによって、リスト内のインデックス番号がnである要素を指すListIteratorを作成することもできます。次の例は、これらすべての機能を示しています。set()
listIterator()
listIterator(n)
import java.util.List;
import java.util.ListIterator;
public class ListIteration {
public static void main(String[] args) {
List<Pet> pets = new PetCreator().list(8);
ListIterator<Pet> it = pets.listIterator();
while(it.hasNext()) {
System.out.print(it.next() +
", " + it.nextIndex() +
", " + it.previousIndex() + "; ");
}
System.out.println();
// Backwards:
while(it.hasPrevious()) {
System.out.print(it.previous().id() + " ");
}
System.out.println();
System.out.println(pets);
it = pets.listIterator(3);
while(it.hasNext()) {
it.next();
it.set(new PetCreator().get());
}
System.out.println(pets);
}
}
new PetCreator().get()
このメソッドは、 List内のすべての Pet オブジェクトを位置 3 から置き換えるのに使用されます。
リンクリスト
LinkedListもArrayListのような基本的なListインターフェイスを実装しますが、Listの途中で挿入および削除操作を実行する場合はArrayListよりも効率的です。ただし、ランダム アクセス操作では効率が低くなります。
LinkedList には、 stack、queue、または deque として使用できるメソッドも追加されています。これらのメソッドの一部は、名前だけが互いに異なる場合もあれば、特定の使用法 (特に Queues ) のコンテキストで名前をより適切にするためにわずかに異なる場合もあります。例えば:
getFirst()
と がelement()
同じ場合、どちらもリストの先頭 (最初の要素) を削除せずに返します。Listが空の場合は、NoSuchElementExceptionがスローされます。peek()
このメソッドは、リストが空の場合にnullを返すという点で、これら 2 つのメソッドとは少し異なります。removeFirst()
と はremove()
同じですが、リストの先頭要素を削除して返し、リストが空の場合はNoSuchElementExceptionをスローします。poll()
若干の違いはありますが、リストが空の場合はnullを返します。addFirst()
リストの先頭に要素を挿入します。offer()
add()
と と同じaddLast()
。どちらもリストの末尾 (最後) に要素を追加します。removeLast()
リストの最後の要素を削除して返します。
次の例は、これらの機能の基本的な類似点と相違点を示しています。ListFeature.javaに示されている動作は繰り返されません。
public class LinkedListFeatures {
public static void main(String[] args) {
LinkedList<Pet> pets = new LinkedList<>(new PetCreator().list(5));
System.out.println(pets);
// Identical:
System.out.println("pets.getFirst(): " + pets.getFirst());
System.out.println("pets.element(): " + pets.element());
// Only differs in empty-list behavior:
System.out.println("pets.peek(): " + pets.peek());
// Identical; remove and return the first element:
System.out.println("pets.remove(): " + pets.remove());
System.out.println("pets.removeFirst(): " + pets.removeFirst());
// Only differs in empty-list behavior:
System.out.println("pets.poll(): " + pets.poll());
System.out.println(pets);
pets.addFirst(new Rat());
System.out.println("After addFirst(): " + pets);
pets.offer(new PetCreator().get());
System.out.println("After offer(): " + pets);
pets.add(new PetCreator().get());
System.out.println("After add(): " + pets);
pets.addLast(new Hamster());
System.out.println("After addLast(): " + pets);
System.out.println("pets.removeLast(): " + pets.removeLast());
}
}
new PetCreator().list()
結果はLinkedListコンストラクターに渡されるため、 LinkedListに値を設定するために使用できます。Queueインターフェースを見ると、 LinkedListelement()
に基づいて、offer()
、およびメソッドが追加され、 Queueのpeek()
実装になることがわかります。Queueの完全な例については、この章の後半で説明します。poll()
remove()
スタックスタック
スタックは「後入れ先出し」(LIFO) コレクションです。スタック上で最後に「プッシュ」された要素が最初に「ポップ」されるため、プッシュダウン スタックと呼ばれることもあります。スタックによく例えられるのは、バネ仕掛けのスタンドを備えたカフェテリア トレイです。最後にロードされたトレイが常に最初に取り出されて使用されます。
Java 1.0 には、設計が不十分であることが判明したStackクラスが付属していました (下位互換性を確保するために、Java の古い設計のバグと共存することを余儀なくされました)。Java 6 では、スタック機能を直接実装するメソッドが含まれるArrayDequeが追加されました。
import java.util.*;
public class StackTest {
public static void main(String[] args) {
Deque<String> stack = new ArrayDeque<>();
for (String s : "My dog has fleas".split(" ")) {
stack.push(s);
}
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
}
}
スタックとして使用する場合でも、Dequeとして宣言する必要があります。Stackというクラスを使用すると、状況がより明確になる場合があります。
import java.util.ArrayDeque;
import java.util.Deque;
public class Stack<T> {
private Deque<T> storage = new ArrayDeque<>();
public void push(T v) {
storage.push(v);
}
public T peek() {
return storage.peek();
}
public T pop() {
return storage.pop();
}
public boolean isEmpty() {
return storage.isEmpty();
}
@Override
public String toString() {
return storage.toString();
}
}
ここでは、ジェネリックスを使用したクラス定義の最も単純な例を紹介します。クラス名の後にこれがパラメーター化された型であることをコンパイラーに伝え、クラスの使用時に型パラメーターT が実際の型に置き換えられます。基本的に、このクラスは「タイプTのオブジェクトを保持できるスタックを定義している」と述べています。スタックはArrayDeque を使用して実装され、 ArrayDequeにはタイプTのオブジェクトを保持することも指示されます。は型Tpush()
のオブジェクトを受け入れ、型Tのオブジェクトを返すことに注意してください。このメソッドはスタックの最上位の要素を返しますが、スタックの最上位から削除するのではなく、最上位の要素を削除して返します。peek()
pop()
peek()
pop()
スタック動作のみが必要な場合は、 ArrayDequeの他のすべてのメソッドを含むクラスが生成されるため、継承の使用は適切ではありません。合成を使用すると、公開するメソッドとその名前の付け方を選択できます。
この新しいStackクラスは、 StackTest.javaの同じコードを使用して以下に示されます。
public class StackTest2 {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String s : "My dog has fleas".split(" ")) {
stack.push(s);
}
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
}
}
このStackクラスを独自のコードで使用する場合は、そのインスタンスを作成するときにパッケージ名を完全に指定するか、クラスの名前を変更する必要があります。そうしないと、java.utilパッケージのStackと競合する可能性があります。たとえば、上記の例でjava.util. をインポートする場合、競合を防ぐためにパッケージ名を使用する必要があります。
public class StackCollision {
public static void main(String[] args) {
Stack<String> stack = new Stack<>();
for (String s : "My dog has fleas".split(" ")) {
stack.push(s);
}
while (!stack.isEmpty()) {
System.out.print(stack.pop() + " ");
}
System.out.println();
java.util.Stack<String> stack2 =
new java.util.Stack<>();
for (String s : "My dog has fleas".split(" ")) {
stack2.push(s);
}
while (!stack2.empty()) {
System.out.print(stack2.pop() + " ");
}
}
}
すでにjava.util.Stackがありますが、ArrayDeque の方がより優れたStackを生成できるため、推奨されます。
「優先」スタック実装の選択は、明示的なインポートを使用して制御することもできます。
import onjava.Stack;
Stackへの参照ではonjavaバージョンが選択されるようになり、java.util.Stack を選択する場合は完全修飾名を使用する必要があります。