52
}
} /* Output-
0 Rat 1 Manx 2 Cymric 3.Mutt 4 Pug 5.Cymric 6 Pug 7 Manx 4:Pug 6 Pug 3 Mutt 1 Manx 5 Cymric 7 Manx 2:Cymric O-Rat O-Rat 1 Manx 2-Cymric 3-Mutt 4-Pug 5 Cymric 6 Pug 7 Manx 4-Pug 6 Pug 3 Mutt 1 Manx 5:Cymric 7.Manx 2 Cymric 0:Rat
{Ralph=Rat. Eric=Manx, Robin=Cymric. Lacey=Mutt. Britney=Pug. Sam=Cymric. Spot=Pug. Fluffy=Manx}
[Ralph. Eric. Robin. Lacey. Britney. Sam. Spot. Fluffy] 0:Rat l.Manx 2-Cymric 3-Mutt 4:Pug 5-Cymric 6:Pug 7 Manx 0:Rat 1 Manx 2-Cymric 3-Mutt 4.Pug 5:Cymric 6:Pug 7 Manx */// ~
Обе версии display() работают как с объектами Map, так и с подтипами Collection; при этом как Collection, так и Iterator изолируют методы display() от знания конкретной реализации используемого контейнера.
В данном случае два решения примерно равноценны. Использование Iterator становится предпочтительным при реализации постороннего класса, для которого реализация интерфейса Collection затруднена или нежелательна. Например, если мы создаем реализацию Collection наследованием от класса, содержащего объекты Pet, нам придется реализовать все методы Collection, даже если они не будут использоваться в методе display(). Хотя проблема легко решается наследованием от AbstractCollection, вам все равно придется реализовать iterator() вместе с size(), чтобы предоставить методы, не реализованные AbstractCollection, но используемые другими методами AbstractCollection:
// • hoidi ng/Col1ecti onSequence.java import typeinfo pets.*; import java.util.*;
public class CollectionSequence extends AbstractCollection<Pet> {
private Pet[] pets = Pets.createArray(8); public int sizeO { return pets.length; } public Iterator<Pet> iteratorO {
return new Iterator<Pet>() {
private int index = 0; public boolean hasNextO. {
return index < pets.length;
public Pet nextО { return pets[index++]; } public void removeО { // He реализован
throw new UnsupportedOperationExceptionO;
}
}:
}
public static void main(String[] args) {
CollectionSequence с = new Col 1ectionSequence(); InterfaceVsIterator.di splay(с); InterfaceVsIterator.di splay(c.i terator());
}
} /* Output:
0:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx 0:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx *///:-
Метод remove() являетсянеобязательной операцией. В нашем примере реа- лизовывать его не нужно, и в случае вызова он выдает исключение.
Из приведенного примера видно, что при реализации Collection вы также реализуете iterator(), а простая отдельная реализация iterator() требует чуть меньших усилий, чем наследование от AbstractCollection. Но, если класс уже наследует от другого класса, наследование еще и от AbstractCollection невозможно. В этом случае для реализации Collection придется реализовать все методы интерфейса, и тогда гораздо проще ограничиться наследованием и добавить возможность создания итератора:
//: hoidi ng/NonCol1ecti onSequence.java import typeinfo.pets.*; import java.util.*;
class PetSequence {
protected Pet[] pets = Pets.createArray(8);
}
public class NonCollectionSequence extends PetSequence { public Iterator<Pet> iteratorO {
return new Iterator<Pet>() {
private int index = 0; public boolean hasNextO {
return index < pets length;
}
public Pet nextO { return pets[index++]; } public void removeO { // He реализован
throw new UnsupportedOperationExceptionO;
}
}:
}
public static void main(String[] args) {
NonCollectionSequence nc = new NonCollectionSequence(); InterfaceVsIterator.display(nc.iteratorO);
}
} /* Output:
0:Rat l:Manx 2:Cymric 3:Mutt 4:Pug 5:Cymric 6:Pug 7:Manx *///:-
Создание Iterator обеспечивает минимальную логическую привязку между последовательностью и методом, использующим эту последовательность, а также налагает гораздо меньше ограничений на класс последовательности, реализующий Collection.
Синтаксис foreach и итераторы
До настоящего момента «синтаксис foreach» использовался в основном с массивами, но он также будет работать с любым объектом Collection. Некоторые примеры уже встречались нам при работе с ArrayList, но можно привести и более общее подтверждение:
//: holding/ForEachCollections java
// Синтаксис foreach работает с любыми коллекциями
import java.util.*,
public class ForEachCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<String>(); Col lections.addAl1(cs,
"Take the long way home".splitC' ")); for(String s : cs)
System, out. pri nt(.. + s + ...... ),
}
} /* Output-
'Take' 'the' 'long' 'way' 'home' *///:-
Поскольку cs является Collection, этот пример показывает, что поддержка foreach является характеристикой всех объектов Collection.
Работа этой конструкции объясняется тем, что в Java SE5 появился новый интерфейс Iterable, который содержит метод iterator() для создания Iterator, и именно интерфейс Iterable используется при переборе последовательности в синтаксисе foreach. Следовательно, создав любой класс, реализующий Iterable, вы сможете использовать его в синтаксисе foreach:
//: hoidi ng/IterableClass.java // Anything Iterable works with foreach. import java.util.*;
public class IterableClass implements Iterable<String> { protected StringE] words = ("And that is how " +
"we know the Earth to be banana-shaped.").splitC "); public Iterator<String> iteratorO {
return new Iterator<String>() { private int index = 0; public boolean hasNextO {
return index < words length;
}
public String nextO { return words[index++]; } public void remove0 { // Not implemented
throw new UnsupportedOperationExceptionO,
};
public static void main(Stnng[] args) {
for(String s • new IterableClassO) System out print(s + " ");
}
} /* Output.
And that is how we know the Earth to be banana-shaped. *///:-
Метод iterator() возвращает экземпляр анонимной внутренней реализации Iterator<string>, последовательно доставляющей каждое слово в массиве. В main() мы видим, что IterableClass действительно работает в синтаксисе foreach.
В Java SE5 многие классы реализуют Iterable, прежде всего все классы Collection (но не Map). Например, следующий код выводит все переменные окружения (environment) операционной системы:
//: holding/Envi ronmentVariables.java import java util *;
public class EnvironmentVariables {
public static void main(String[] args) {
for (Map Entry entry System getenvO .entrySetO) { System.out.println(entry.getKey() + ": " + entry. getValueO);
}
}
} /* (Выполните, чтобы увидеть результат) *///:-
System.getenv() возвращает Map, entrySet() создает Set с элементами Map.Entry, a Set поддерживает Iterable и поэтому может использоваться в цикле foreach.
Синтаксис foreach работает с массивами и всем, что поддерживает Iterable, но это не означает, что массив автоматически поддерживает Iterable:
// ■ hoiding/ArraylsNotIterable.java import java.util.*;
public class ArraylsNotlterable {
static <T> void test(Iterable<T> ib) { for(T t • ib)
System.out.print(t + " ");
}
public static void main(String[] args) { test(Arrays.asList(l. 2, 3)); StringC] strings = { "А", "В". "С" }: // Массив работает в foreach, но не является Iterable: //! test(strings);
// его необходимо явно преобразовать к Iterable: testCArrays.asLi st(stri ngs));
}
} /* Output: 1 2 3 А В С *///•-
Попытка передачи массива в аргументе Iterable завершается неудачей. Автоматическое преобразование в Iterable не производится; его необходимо выполнять вручную.
Идиома «метод-адаптер»
Что делать, если у вас имеется существующий класс, реализующий Iterable, и вы хотите добавить новые способы использования этого класса в синтаксисе foreach? Допустим, вы хотите иметь возможность выбора между перебором списка слов в прямом или обратном направлении. Если просто воспользоваться наследованием от класса и переопределить метод iterator, то существующий метод будет заменен и никакого выбора не будет.
Одно из решений этой проблемы основано на использовании идиомы, которую я называю «методом-адаптером». Термин «адаптер» происходит от одноименного паттерна: вы должны предоставить интерфейс, необходимый для работы синтаксиса foreach. Если у вас имеется один интерфейс, а нужен другой, проблема решается написанием адаптера. В данном случае требуетсядобавитьк стандартному «прямому» итератору обратный, так что переопределение исключено. Вместо этого мы добавим метод, создающий объект Iterable, который может использоваться в синтаксисе foreach. Как будет показано далее, это позволит нам предоставить несколько вариантов использования foreach:
//: hoiding/AdapterMethodldiom.java
// Идиома "метод-адаптер" позволяет использовать foreach
// с дополнительными разновидностями Iterable.
import java.util.*;
class ReversibleArrayList<T> extends ArrayList<T> {
public ReversibleArrayList(Collection<T> c) { super(c); }. public Iterable<T> reversedO {
return new Iterable<T>() {
public Iterator<T> iteratorO {
return new Iterator<T>() {
int current = sizeO - 1,
public boolean hasNextO { return current > -1;
}
public T nextO { return get (current--); } public void removeO { // He реализован throw new
UnsupportedOperationExceptionO;
}
} •
}
}:
}
}
public class AdapterMethodldiom {
public static void main(String[] args) { ReversibleArrayList<String> ral =
new ReversibleArrayList<String>(
Arrays.asList(To be or not to be".splitC' "))): // Получаем обычный итератор, полученный при помощи iteratorO: forCString s : ral)
System.out.print(s + " "); System.out printlnO;
// Передаем выбранный нами Iterable forCString s • ral .reversedO)
System.out.print(s + " "),
}
} /* Output To be or not to be be to not or be To */// ~
Если просто поместить объект ral в синтаксис foreach, мы получим (стандартный) «прямой» итератор. Но если вызвать для объекта reversed(), поведение изменится.
Использовав этот прием, можно добавить в пример IterableClass.java два метода-адаптера:
// hoidi ng/MultiIterableClass.java // Adding several Adapter Methods, import java util *;
public class MultilterableClass extends IterableClass { public Iterable<String> reversedO {
return new Iterable<String>() {
public Iterator<String> iteratorO {
return new Iterator<String>() {
int current = words length - 1,
public boolean hasNextO { return current > -1;
}
public String nextO { return words[current--];
}
public void removeО { // He реализован throw new
UnsupportedOperationException(),
}
}:
}
}.
}
public Iterable<String> randomizedO { return new Iterable<String>() {
public Iterator<String> iteratorO { List<String> shuffled =
new ArrayList<String>(Arrays.asList(words)); Collections.shuffleCshuffled, new Random(47)); return shuffled.iterator();
}
}:
}
public static void main(String[] args) {
MultilterableClass mic = new MultiIterableClassO; for (String s : mic. reversedO)
System out print(s + " "): System, out. pri ntlnO. for(String s : mic.randomizedO)
System out.print(s + " "); System.out.prmtlnO:продолжение &for(String s : mic)
System.out.print(s + " ");
}
} /* Output:
banana-shaped, be to Earth the know we how is that And is banana-shaped. Earth that how the be And we know to And that is how we know the Earth to be banana-shaped *///:-
Из выходных данных видно, что метод Collections.shuffle не изменяет исходный массив, а только переставляет ссылки в shuffled. Так происходит только потому, что метод randomized() создает для результата Arrays.asList() «обертку» в виде ArrayList. Если бы операция выполнялась непосредственно с объектом List, полученным от Arrays.asList(), то это привело бы к изменению нижележащего массива: