Foreach and iterator
So far, the foreach syntax has been used primarily for arrays, but it can also be applied to any Collection object. You’ve actually seen many examples of ArrayList being used with it, but here’s a more general proof:
package p10;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
public class ForEachCollections {
public static void main(String[] args) {
Collection<String> cs = new LinkedList<>();
Collections.addAll(cs,"Take the long way home".split(""));
for(String s:cs){
System.out.print("'" + s + "'");
}
/** * 'Take' 'the' 'long' 'way' 'home' */}}Copy the code
Since CS is a Collection, this code shows that being able to work with foreach is a feature of all Collection objects.
It works because Java SE5 introduced a new interface called Iterable, which contains an Iterator () method that produces iterators, and the Iterable interface is used by Foreach to move through sequences. So if you create any classes that implement Iterable, you can use them in foreach statements:
package p10;
import java.util.Iterator;
public class IterableClass implements 可迭代<String> {
protected String[] words = ("And that is how " +
"we know the Earth to be banana-shaped.").split("");
@Override
public Iterator<String> iterator(a) {
return new Iterator<String>() {
private int cursor = 0;
@Override
public boolean hasNext(a) {
return cursor < words.length;
}
@Override
public String next(a) {
returnwords[cursor++]; }}; }public static void main(String[] args) {
for(String s:new IterableClass()){
System.out.print(s + "");
}
/** * And that is how we know the Earth to be banana-shaped. */}}Copy the code
The iterator() method returns an instance of an anonymous inner class that implements iterator and iterates through all the words in the array. In main(), you can see that IterableClass can indeed be used in foreach statements.
In Java SE5, a large number of classes are of type Iterable, mainly including all Collection classes (but not the various Maps).
Foreach statements can be used for arrays or any other Iterable, but this does not mean that an array is an Iterable, and any auto-wrapping does not automatically occur:
package p10;
import java.util.Arrays;
public class ArrayIsNotIterable {
static <T> void test(Iterable<T> ib){
for(T t:ib){
System.out.print(t + ""); }}public static void main(String[] args) {
test(Arrays.asList(1.2.3));
String[] strings = {"A"."B"."C"};
// An array works in foreach, but it's not Iterable
/ /! test(strings)
// You must explicitly convert it to an Iterable
test(Arrays.asList(strings));
/** * 1 2 3 A B C */}}Copy the code
Trying to pass an array as an Iterable argument will fail. This means that there is no automatic conversion from array to Iterable; you must do it manually.
Usage of the adapter method
If you have an Iterable class and you want to add one or more methods that use that class in foreach statements, how do you do that? For example, suppose you want to have the option of iterating a list of words forward or backward. If you inherit this class directly and override iterator(), you can only replace existing methods, not implement selection. One solution is the idiom of the so-called adapter approach. The “adapter” part comes from the design pattern, because you have to provide specific interfaces to satisfy foreach statements. When you have one interface and need another, writing an adapter solves the problem. Here, I want to add the ability to produce reverse iterators on top of the default forward iterator, so instead of using overwrites, I add a method that produces Iterable objects that can be used in foreach statements. As you can see, this allows us to provide multiple ways to use Foreach:
package p10;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
public class AdapterMethodIdiom{
public static void main(String[] args) {
ReversibleArrayList<String> ral = new ReversibleArrayList<>(Arrays.asList("To be or not to be".split("")));
for(String s:ral){
System.out.print(s + "");
}
System.out.println();
// Hand it the Iterable of your choice
for(String s : ral.reversed()){
System.out.print(s + "");
}
/** * To be or not to be * be to not or be To */}}class ReversibleArrayList<T> extends ArrayList<T> {
public ReversibleArrayList(Collection<T> c){
super(c);
}
public Iterable<T> reversed(a){
return new Iterable<T>() {
@Override
public Iterator<T> iterator(a) {
return new Iterator<T>(){
int cursor = size() - 1;
@Override
public boolean hasNext(a) {
return cursor > -1;
}
@Override
public T next(a) {
returnget(cursor--); }}; }}; }; }Copy the code
If you place a RAL object directly into a FOREach statement, you get a (default) forward iterator. But if you call the reversed() method on the object, you will have different behavior. Using this approach, I can add two adapter methods to the Iterableclass.java example:
package p10;
import java.util.*;
public class MultiIterableClass extends IterableClass{
public Iterable<String> reversed(a){
return new Iterable<String>() {
@Override
public Iterator<String> iterator(a) {
return new Iterator<String>() {
int cursor = words.length - 1;
@Override
public boolean hasNext(a) {
return cursor > -1;
}
@Override
public String next(a) {
returnwords[cursor--]; }}; }}; }public Iterable<String> randomized(a){
return new Iterable<String>() {
@Override
public Iterator<String> iterator(a) {
List<String> shuffled = new ArrayList<>(Arrays.asList(words));
Collections.shuffle(shuffled,new Random(47));
returnshuffled.iterator(); }}; }public static void main(String[] args) {
MultiIterableClass mic = new MultiIterableClass();
for(String s:mic.reversed()){
System.out.print(s + "");
}
System.out.println();
for(String s:mic.randomized()){
System.out.print(s + "");
}
System.out.println();
for(String s:mic){
System.out.print(s + "");
}
/** * 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. */}}Copy the code
Note that the second method, random(), does not create its own Iterator, but returns the Iterator directly from the scrambled List. As you can see from the output, the collection.shuffe () method does not affect the original array, but merely scrambles the references in shuffied. This only happened because the randomized() method wrapped the results of the array.aslist () method in an ArrayList. If the List generated by the arrays.aslist () method is scrambled directly, it will modify the underlying array like this:
package p10;
import java.util.*;
public class ModifyingArraysAsList {
public static void main(String[] args) {
Random random = new Random(47);
Integer[] ia = {1.2.3.4.5.6.7.8.9.10};
List<Integer> list1 = new ArrayList<>(Arrays.asList(ia));
System.out.println("Before shuffling: " + list1);
Collections.shuffle(list1,random);
System.out.println("After shuffling: " + list1);
System.out.println("array: " + Arrays.toString(ia));
List<Integer> list2 = Arrays.asList(ia);
System.out.println("Before shuffling: " + list2);
Collections.shuffle(list2,random);
System.out.println("After shuffling: " + list2);
System.out.println("array: " + Arrays.toString(ia));
/** * Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * After shuffling: [4, 6, 3, 1, 8, 7, 2, 5, 10, 9] * array: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * Before shuffling: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] * After shuffling: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] * array: [9, 1, 6, 3, 7, 2, 5, 10, 4, 8] */}}Copy the code
In the first case, the output of arrays.aslist () is passed to the constructor of ArrayList(), which creates an ArrayList that references elements of IA, so messing with those references doesn’t modify the array. However, if you use the result of arrays.asList (ia) directly, this scrambling will modify the order of Ia. It’s important to realize that the List objects generated by arrays.asList () use the underlying array as their physical implementation. ** As long as you perform operations that modify the List and you don’t want the original array to be modified, you should create a copy in another container.