目录
Collection和Iterator的对比
for-in和迭代器
总结图
本笔记参考自: 《On Java 中文版》
Collection是所有序列集合的共同根接口。因此,可以认为它是一个为表示其他接口之间的共性而出现的“附属接口”。
java.util.AbstractCollection提供了一个Collection的默认实现,所以可以通过创建AbstractCollection的新子类来避免不必要的代码重复。这种接口存在的另一个理由是,通过面向接口的编程方式,我们的代码可以变得更加通用。一个实现了接口的方法也可以应用于任何的Collection类型。
在Java中,实现Collection需要提供iterator()方法,这就将迭代器和集合捆绑起来了。
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.List;
public class InterfaceVsIterator {
public static void display(Iterator it) {
while (it.hasNext()) {
Pet p = it.next();
System.out.print(p.id() + ": " + p + " ");
}
System.out.println();
}
public static void display(Collection pets) {
for (Pet p : pets)
System.out.print(p.id() + ": " + p + " ");
System.out.println();
}
public static void main(String[] args) {
List petList = new PetCreator().list(8);
Set petSet = new HashSet<>(petList);
Map petMap = new LinkedHashMap<>();
String[] names = ("拉尔夫, 埃里克, 罗宾, 蕾西, " + "布里特妮, 山姆, 斑点, 路威").split(", ");
for (int i = 0; i < names.length; i++)
petMap.put(names[i], petList.get(i));
// Collection
display(petList);
display(petSet);
// Iterator
System.out.println();
display(petList.iterator());
display(petSet.iterator());
System.out.println();
System.out.println(petMap);
System.out.println(petMap.keySet());
display(petMap.values());
display(petMap.values().iterator());
}
}
程序执行的结果是:
(笔者使用的是JDK 11,输出结果与《On Java》中有所出入。推测是因为哈希的实现有区别。)
在上述程序中可以发现,Collection和Iterator都实现了解耦,display()方法不需要理解底层集合的特定实现。
若要实现一个不是Collection的外部类,让其实现Collection接口可能会很麻烦或是复杂的,这时就会体现出Iterator的优势了。下面的例子将会继承AbstractCollection类:
import java.util.AbstractCollection;
import java.util.Iterator;
public class CollectionSequence extends AbstractCollection {
private Pet[] pets = new PetCreator().array(8); // 返回一个Pet[]数组
@Override
public int size() { // 必须实现的接口方法size()
return pets.length;
}
@Override
public Iterator iterator() { // 必须自己提供iterator()方法
return new Iterator() { // Java的类型推断能力有限,所以这里还是需要标明类型
private int index = 0;
@Override
public boolean hasNext() {
return index < pets.length;
}
@Override
public Pet next() {
return pets[index++];
}
@Override
public void remove() { // remove是可选的实现,就算这里不进行实现也没有关系
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
CollectionSequence c = new CollectionSequence();
InterfaceVsIterator.display(c);
InterfaceVsIterator.display(c.iterator());
}
}
程序执行的结果如下:
这个例子实现了一个Collection,为此还提供了一个iterator()的实现。但这里我们就发现,与继承AbstractCollection类相比,只实现iterator()所需的工作并没有减少太多。另外,实现Collection还需要提供我们并不需要使用的其他方法的实现。
所以,先继承,在添加创建迭代器的能力,这样会比较轻松:
生成一个Iterator,是将序列与处理序列的方法连接起来的耦合性最低的方法。与Collection相比,这种做法对序列类的约束会少的多。
for-in语法可以配合任何Collection对象使用:
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedList;
public class ForInCollections {
public static void main(String[] args) {
Collection cs = new LinkedList<>();
Collections.addAll(cs, "不吃葡萄倒吐葡萄皮".split(""));
for (String s : cs)
System.out.print("'" + s + "'");
System.out.println();
}
}
程序执行的结果如下:
for-in语句之所以能这么做,其原理是因为Java 5引入了一个叫做Iterable的接口,这个接口包含的iterator()方法会生成一个Iterator。for-in使用这个Iterable接口遍历序列。
举一反三,若我们创建了一个实现了Iterable接口的类,那么这个类也就可以被用于for-in语句中:
import java.util.Iterator;
public class IterableClass implements Iterable {
protected String[] words = ("扁担没有板凳宽,板凳没有扁担长".split(""));
@Override
public Iterator iterator() {
return new Iterator() {
private int index = 0;
@Override
public boolean hasNext() {
return index < words.length;
}
@Override
public String next() {
return words[index++];
}
@Override
public void remove() {
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for (String s : new IterableClass())
System.out.print(s + " ");
System.out.println();
}
}
程序执行的结果如下:
for-in语句可以配合数组或实现了Iterable接口的类进行使用,但这并不是说数组也自动实现了Iterable,也并不存在任何装箱操作:
import java.util.Arrays;
public class ArrayIsNotIterable {
static void test(Iterable ib) {
for (T t : ib)
System.out.print(t + " ");
System.out.println();
}
public static void main(String[] args) {
test(Arrays.asList(1, 2, 3));
String[] strings = { "A", "B", "C" };
// 数组可以配合for-in进行使用
// 但并没有实现Iterable接口,因此无法作为参数传入test()
// test(strings);
// 必须将strings显式地转换为Iterable:
test(Arrays.asList(strings));
}
}
程序执行的结果如下:
适配器方法惯用法
当我们需要以不止一种方式将类用在for-in语句时,继承就不能很好地满足我们了。此时,我们需要做的是提供一个满足for-in语句要求的特定接口。比如:默认的迭代器是向前的,若我们还需要进行向后的迭代,那么一个好的方法就是添加一个生成Iterable对象的方法:
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
class ReversibleArrayList extends ArrayList {
ReversibleArrayList(Collection c) {
super(c);
}
public Iterable reversed() { // 添加一个迭代器
return new Iterable() {
public Iterator iterator() {
return new Iterator() {
int current = size() - 1;
@Override
public boolean hasNext() {
return current > -1;
}
@Override
public T next() {
return get(current--);
}
@Override
public void remove() { // 未实现
throw new UnsupportedOperationException();
}
};
}
};
}
}
public class AdapterMethodIdiom {
public static void main(String[] args) {
ReversibleArrayList ral = new ReversibleArrayList<>(
Arrays.asList("To be continued".split(" ")));
// 通过iterator()获得原始的迭代器
for (String s : ral)
System.out.print(s + " ");
System.out.println();
// 使用自建的迭代器
for (String s : ral.reversed())
System.out.print(s + " ");
System.out.println();
}
}
程序执行的结果是:
在main(),可以看见调用ral和ral.reversed()产生的是不同的行为。
另外,迭代器也可以通过使用其他类来实现:
上述这个类将会返回一个被打乱的Iterator。
最后再提一下Arrays.asList(),这个方法会返回一个包装过的ArrayList。若将包装后的ArrayList传递给Collections.shuffle(),那么原始数组不会受到影响。但若直接将Arrays.asList()生成的List打乱顺序,那么shuffle()方法会改变底层数组:
import java.util.ArrayList;
import java.util.List;
import java.util.Arrays;
import java.util.Collections;
import java.util.Random;
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 };
List list1 = new ArrayList<>(Arrays.asList(ia)); //
System.out.println("在乱序之前:" + list1);
Collections.shuffle(list1, rand);
System.out.println("在乱序之后:" + list1);
System.out.println("数组本身:" + Arrays.toString(ia));
System.out.println();
List list2 = Arrays.asList(ia);
System.out.println("在乱序之前:" + list2);
Collections.shuffle(list2, rand);
System.out.println("在乱序之后:" + list2);
System.out.println("数组本身:" + Arrays.toString(ia));
}
}
程序执行的结果如下:
注意:Arrays.asList()产生的List对象,会将原本的底层数组作为其物理实现。所以,若需要修改List,最好将其复制到另一个集合中。