Синтаксис 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(), то это привело бы к изменению нижележащего массива:
//- hoiding/ModifyingArraysAsList.java 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> listl =
new ArrayList<Integer>(Arrays.asList(ia));
System.out.printIn("До перестановки. " + listl);
Col 1ecti ons.shuff1e(1i st1, rand);
System.out.println("После перестановки: " + listl);
System.out.printlnf'Массив: " + Arrays.toString(ia)),
List<Integer> list2 = Arrays.asList(ia);
System.out.println("До перестановки: " + list2);
Col 1 ecti ons. shuffled i st2. rand);
System.out.println("После перестановки: " + list2);
System.out.println("Массив: " + Arrays.toString(ia));
}
} /* Output:
До перестановки: [1, 2, 3. 4, 5. 6. 7, 8, 9, 10] После перестановки: [4. 6, 3, 1. 8, 7, 2, 5. 10. 9] Массив: [1, 2, 3. 4. 5. 6. 7, 8. 9. 10] До перестановки: [1, 2. 3, 4, 5, 6. 7. 8, 9, 10] После перестановки: [9, 1. 6. 3. 7, 2. 5, 10, 4, 8] Массив- [9. 1. 6. 3. 7, 2, 5. 10. 4. 8] *///:-
В первом случае вывод Arrays.asList() передается конструктору ArrayList(), а последний создает объект ArrayList, ссылающийся на элементы ia. Перестановка этих ссылок не изменяет массива. Но, если мы используем результат Arrays.asList(ia) напрямую, перестановка изменит порядок ia. Важно учитывать, что Arrays.asList() создает объект List, который использует нижележащий массив в качестве своей физической реализации. Если с этим объектом List выполняются какие-либо изменяющие операции, но вы не хотите изменения исходного массива, создайте копию в другом контейнере.
В Java существует несколько способов хранения объектов:
• В массивах объектам назначаются числовые индексы. Массив содержит объекты заранее известного типа, поэтому преобразование типа при выборке объекта не требуется. Массив может быть многомерным и может использоваться для хранения примитивных типов. Тем не менее изменить размер созданного массива невозможно.
• В Collection хранятся отдельные элементы, а в Map — пары ассоциированных элементов. Механизм параметризации позволяет задать тип объектов, хранимых в контейнере, поэтому поместить в контейнер объект неверного типа невозможно, и элементы не нуждаются в преобразовании типа при выборке. И Collection, и Map автоматически изменяются в размерах при добавлении новых элементов. В контейнерах не могут храниться примитивы, но механизм автоматической упаковки автоматически создает объектные «обертки», сохраняемые в контейнере.