答案是肯定的,方式有两种:迭代器和 foreach 语法。
迭代器是一个对象,它的工作就是遍历并选择序列中的对象,而客户端程序员不需要知道序列的底层结构。同时迭代器也被称为轻量级对象,创建它的代价较小。
Java 的 Iterator 只能单向移动,功能如下:
/**
* 迭代器遍历
* @param its
*/
public static String display(Iterator its) {
String str = "";
//判断迭代器中是否还有元素
while(its.hasNext()){
//获取下一个元素
Integer next = its.next();
str = str + next + ",";
//删除 next 元素
its.remove();
}
return str;
}
public static void main(String[] args) {
//创建整数集合
List list = Arrays.asList(1, 3, 2, 5, 4);
//创建不同类型的容器
ArrayList arrayList = new ArrayList<>(list);
LinkedList linkedList = new LinkedList<>(list);
HashSet hashSet = new HashSet<>(list);
TreeSet treeSet = new TreeSet<>(list);
//使用迭代器打印容器元素
System.out.println("arrayList:" + display(arrayList.iterator()));
System.out.println("删除后的 arayList:" + display(arrayList.iterator()));
System.out.println("linkedList:" + display(linkedList.iterator()));
System.out.println("hashSet:" + display(hashSet.iterator()));
System.out.println("treeSet:" + display(treeSet.iterator()));
}
结果如下:
arrayList:1,3,2,5,4,
删除后的 arayList:
linkedList:1,3,2,5,4,
hashSet:1,2,3,4,5,
treeSet:1,2,3,4,5,
从代码中就可以看出,display() 方法根本不需要关心它所遍历的序列的类型信息,这也正是 Iterator 的独到之处,能够将遍历序列的操作与序列底层的结构分离。
当容器通过 iterator() 方法返回 Iterator 对象后,这时候就不需要再为容器中的元素的数量操心了,因为这些事情交给 hashNext() 和 next() 去关心就行了。remove() 方法是用来删除 next() 产生的最后一个元素,所以在调用 remove() 方法前必须先调用 next() 方法。
等等,为啥容器类可以调用 iterator() 方法?接着往下看:
Collection 是所有序列容器的根接口,我们再看一眼它的框架图:
由于 Collection 接口继承了 Iterable 接口,而 Iterable 接口中有 iterator() 抽象方法,这就意味着实现 Collection 接口就必须实现 iterator() 方法来返回 Iterator 对象。
那么问题来了,如果需要创建一个没有实现 Collection 的类时,让它去实现 Collection 接口是非常麻烦的事情,因为这意味着你需要实现它的所有方法,那么 使用 Iterator 就非常吸引人了,此时,只需要写一个方法然后返回 Iterator 对象就可以了。
public class IteratorTest {
//创建 int 数组
private int[] arrays = {1, 2, 3, 4, 5};
/**
* 迭代器方法
* @return
*/
public Iterator iterator() {
//以匿名内部类的方式返回 Iterator 对象
return new Iterator() {
private int index = 0;
@Override
public boolean hasNext() {
return index < arrays.length;
}
@Override
public Integer next() {
return arrays[index++];
}
};
}
/**
* 迭代器遍历
* @param its
*/
public static String display(Iterator its) {
String str = "";
//判断迭代器中是否还有元素
while (its.hasNext()) {
//获取下一个元素
Integer next = its.next();
str = str + next + ",";
}
return str;
}
public static void main(String[] args) {
IteratorTest itTest = new IteratorTest();
System.out.println(display(itTest.iterator()));
}
}
打印如下:
1,2,3,4,5,
上述代码自定义了 iterator() 方法,并且通过匿名内部类的方式返回 Iterator 对象,并重写了其中的 hasNext() 方法和 next() 方法,从而达到了创建迭代器的目的。
这里补充一下 ListIterator,ListIterator 是一个更加强大的 Iterator 的子类型,它只能用于各种 LIst 类的访问。它有以下特点:
public static void main(String[] args) {
//创建 list
List list = Arrays.asList(1,2,3,4,5);
//通过 list 获取索引为 1 的 listIterator 对象
ListIterator listIterator = list.listIterator(1);
while(listIterator.hasNext()){
System.out.println("当前元素:" + listIterator.next() +
",下一个元素索引:" + listIterator.nextIndex() +
",上一个元素索引:" + listIterator.previousIndex() + ";");
}
//向前移动
while(listIterator.hasPrevious()){
System.out.println("向前移动:" + listIterator.previous());
}
//向后移动
while(listIterator.hasNext()){
//获取当前元素
listIterator.next();
//将元素的值设置为 10
listIterator.set(10);
}
System.out.println("修改后的集合:" + list);
}
结果如下:
当前元素:3,下一个元素索引:3,上一个元素索引:2;
当前元素:4,下一个元素索引:4,上一个元素索引:3;
当前元素:5,下一个元素索引:5,上一个元素索引:4;
向前移动5
向前移动4
向前移动3
向前移动2
向前移动1
修改后的集合:[10, 10, 10, 10, 10]
代码直接通过 listIterator(1) 方法获取指向索引为 1 的 ListIterator,并通过 nextIndext() 和 previousIndex() 方法来获取当前元素的前一个和后一个元素的索引,通过 hasPrevious() 方法来是否还有上一个元素,通过 previous() 方法来获取当前元素,并指向上一个元素,set() 方法可对当前元素进行修改操作。
到目前为止,foreach 语法主要用于数组和任何 Collection 对象,这点我们在 数组初始化 和 Java容器 中已经介绍过,这里就不再赘述。
之所以能工作,是因为 Collection 继承了 Iterable 接口,原因上面已经说过了,Iterable 接口被 foreach 用来在序列中移动。因此,任何实现了 Iterable 的类,都可以将它用在 foreach 语句中。
简单示例:
public class IterableTest implements Iterable {
//创建整型数组
private Integer[] ints = {1, 2, 3, 4, 5};
/**
* 实现 iterator 方法
* @return
*/
@Override
public Iterator iterator() {
return new Iterator() {
private int index = 0;
@Override
public boolean hasNext() {
return index < ints.length;
}
@Override
public Integer next() {
return ints[index++];
}
};
}
public static void main(String[] args) {
//foreach 遍历并打印
for (Integer i : new IterableTest()) {
System.out.println(i);
}
}
}
打印结果:
1
2
3
4
5
上述代码中,创建了一个去实现 Iterable 接口的类,并实现了其中的 iterator() 方法,该方法以匿名内部类的方式返回了 Iterable 的实例,该匿名内部类可以遍历数组中所有的元素。
foreach 语句可以用于数组或其他任何的 Iterable,但是这并不意味着数组肯定也是一个 Iterable,而任何自动包装也不会发生:
/**
* 遍历 iterable
*
* @param iterable
* @param
*/
public static void display(Iterable iterable) {
for (T t : iterable) {
System.out.print(t + ",");
}
}
public static void main(String[] args) {
//创建 Integer 数组
Integer[] ints = {1, 2, 3};
//将数组转换成 List 然后调用 display 方法
display(Arrays.asList(ints));
//调用 display 方法报错
//display(ints);
}
代码尝试将数组当做 Iterable 对象传递给 display() 方法,但是失败了,但是将数组转换成 List 对象传递给 display() 方法成功了。这说明不存在任何从数组到 Iterable 的自动转换,必须手动转换。
补充一下,如果数组是基本数据类型,比如 int,那么将其转换为 List 集合然后将其当做 Iterable 参数传递的时候,虽然编译不会报错,但是结果是错误的,因为 Java 中泛型要求是对象类型,而基本数据类型不是对象,需要使用其包装类才行。
本章介绍了迭代器的概念以及基本使用,介绍了 Collection 和 Iterable,foreach 语法和 Iterable 之间的联系,补充说明了 ListIterator 这个可以双向移动的迭代器。
####欢迎关注公众号:柯妞妞