Итераторы
В любом контейнерном классе вы должны иметь способ поместить вещь внутри и способ достать вещь наружу. Кроме этого, первичная задача контейнера — хранить вещи. В случае ArrayList: add( ) - способ, который вставляет объекты, а get( ) - один из способов получит вещи наружу. ArrayList достаточно гибок, вы можете выбрать все что угодно в любое время и выбирать различные элементы одновременно, используя разные индексы.
Если вы хотите начать думать на более высоком уровне, то есть препятствие: вам необходимо знать точный тип контейнера для правильного его использования. Сначала это может не показаться плохим, но что, если вы начнете использовать ArrayList, а позже в вашей программе вы обнаружите, что в связи со способом использования контейнера более эффективным будет использование LinkedList вместо него? Или, предположим, вы хотите написать кусок общего кода, который не будет знать или заботится о типе контейнера, с которым он работает, так что может ли он использовать разные типы контейнеров без переписывания кода?
Концепция итераторов может быть использована для достижения этой абстракции. Итератор - это объект, чья работа заключается в перемещении по последовательности объектов и выборе каждого объекта в такой последовательности, чтобы клиентский программист не знал или не заботился о подлежащей структуре этой последовательности. Кроме того, итераторы это обычно то, что называется “легковесными” объектами: объекты, дешевые в создании. По этому, вы часто будите находить несколько странными на вид ограничения для итераторов; например, некоторые итераторы могут перемещаться только в одном направлении.
Java Iterator - это пример итератора с такого рода ограничениями. Вы многое можете делать с ним, включая:
Просить контейнер передать вам Iterator, используя метод, называемый iterator( ). Этот Iterator будет готов к возврату первого элемента последовательности при первом вызове метода next( ).
Получать следующий объект в последовательности с помощью next( ).
Проверять есть ли еще объекты в последовательности с помощью hasNext( ).
Удалять последний элемент, возвращенный итератором, с помощью remove( ).
Это все. Это простая реализация итератора, но достаточно мощная (и существуют более изощренный
ListIterator для
List). Чтобы посмотреть, как это работает, позвольте вновь использовать программу
CatsAndDogs.java, введенную ранее в этой главе. В оригинальной версии метод
get( ) был использован для выбора каждого элемента, но в следующей измененной версии используется итератор:
//: c09:CatsAndDogs2.java
// Простой контейнер с итератором.
import java.util.*;
public class CatsAndDogs2 { public static void main(String[] args) { ArrayList cats = new ArrayList(); for(int i = 0; i < 7; i++) cats.add(new Cat(i)); Iterator e = cats.iterator(); while(e.hasNext()) ((Cat)e.next()).print(); } } ///:~
Вы можете видеть, что последние несколько строк используют
Iterator, чтобы пройти по последовательности вместо цикла
for. С помощью итератора вам нет необходимости заботится о числе элементов в контейнере. Об этом беспокоится за вас
hasNext( ) и
next( ).
В качестве другого примера, рассмотрим создание метода печати общего назначения:
//: c09:HamsterMaze.java
// Использование итератора.
import java.util.*;
class Hamster { private int hamsterNumber; Hamster(int i) { hamsterNumber = i; } public String toString() { return "This is Hamster #" + hamsterNumber; } }
class Printer { static void printAll(Iterator e) { while(e.hasNext()) System.out.println(e.next()); } }
public class HamsterMaze { public static void main(String[] args) { ArrayList v = new ArrayList(); for(int i = 0; i < 3; i++) v.add(new Hamster(i)); Printer.printAll(v.iterator()); } } ///:~
Пристальнее всмотритесь в метод
printAll( ). Обратите внимание, что здесь нет информации о типе последовательности. Все что у вас есть - это
Iterator, и это все, что вам нужно знать о последовательности: так как вы можете получить следующий объект и так как вы знаете, когда вы подойдете к концу. Эта идея, получение контейнера объектов и прохождение по нему для выполнения операции для каждого элемента - достаточно мощная и будет просматриваться повсюду в этой книге.
Пример является более общим, так как он косвенным образом использует метод
Object.toString( ). Метод
println( ) перегружается для всех примитивных типов так же, как и для
Object; в каждом случае автоматически производится
String путем вызова соответствующего метода
toString( ).
Хотя в этом нет необходимости, но вы можете быть более точны при использовании приведения, которое имеет тот же эффект, что и вызов
toString( ):
System.out.println((String)e.next());
Однако в общем случае вы захотите сделать что-то большее, нежели вызов методов
Object, так что вы вновь будете применять приведение типов. Вы должны принимать во внимание, что вам интереснее получить
Iterator для последовательности определенного типа и приводить результирующие объекты к этому типу (если вы ошиблись, получите исключение времени выполнения).