Итераторы
В любом контейнерном классе вы должны иметь способ поместить вещь внутри и способ достать вещь наружу. Кроме этого, первичная задача контейнера — хранить вещи. В случае ArrayList: add( ) - способ, который вставляет объекты, а get( ) - один из способов получит вещи наружу. ArrayList достаточно гибок, вы можете выбрать все что угодно в любое время и выбирать различные элементы одновременно, используя разные индексы.
Если вы хотите начать думать на более высоком уровне, то есть препятствие: вам необходимо знать точный тип контейнера для правильного его использования. Сначала это может не показаться плохим, но что, если вы начнете использовать ArrayList, а позже в вашей программе вы обнаружите, что в связи со способом использования контейнера более эффективным будет использование LinkedList вместо него? Или, предположим, вы хотите написать кусок общего кода, который не будет знать или заботится о типе контейнера, с которым он работает, так что может ли он использовать разные типы контейнеров без переписывания кода?
Концепция итераторов может быть использована для достижения этой абстракции. Итератор - это объект, чья работа заключается в перемещении по последовательности объектов и выборе каждого объекта в такой последовательности, чтобы клиентский программист не знал или не заботился о подлежащей структуре этой последовательности. Кроме того, итераторы это обычно то, что называется “легковесными” объектами: объекты, дешевые в создании. По этому, вы часто будите находить несколько странными на вид ограничения для итераторов; например, некоторые итераторы могут перемещаться только в одном направлении.
Java Iterator - это пример итератора с такого рода ограничениями. Вы многое можете делать с ним, включая:
Это все. Это простая реализация итератора, но достаточно мощная (и существуют более изощренный 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 для последовательности определенного типа и приводить результирующие объекты к этому типу (если вы ошиблись, получите исключение времени выполнения).