46

46


// • 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(), то это привело бы к изменению нижележаще­го массива:



//- 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 автоматически изменяются в разме­рах при добавлении новых элементов. В контейнерах не могут храниться примитивы, но механизм автоматической упаковки автоматически созда­ет объектные «обертки», сохраняемые в контейнере.

Report Page