Foreach和迭代器
到目前为止,foreach语法次要用于数组,然而它也能够利用于任何Collection对象。你实际上曾经看到过很多应用ArrayList时用到它的示例,上面是一个更通用的证实:
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'
*/
}
}
复制代码
因为cs是一个Collection,所以这段代码展现了可能与foreach一起工作是所有Collection对象的个性。
之所以可能工作,是因为Java SE5引入了新的被称为Iterable的接口,该接口蕴含一个可能产生Iterator的iterator()办法,并且Iterable接口被foreach用来在序列中挪动。因而如果你创立了任何实现Iterable的类,都能够将它用于foreach语句中:
package p10;
import java.util.Iterator;
public class IterableClass implements Iterable<String> {
protected String[] words = ("And that is how " +
"we know the Earth to be banana-shaped.").split(" ");
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
private int cursor = 0;
@Override
public boolean hasNext() {
return cursor < words.length;
}
@Override
public String next() {
return words[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.
*/
}
}
复制代码
iterator()办法返回的是实现了Iterator的匿名外部类的实例,该匿名外部类能够遍历数组中的所有单词。在main()中,你能够看到IterableClass的确能够用于foreach语句中。
在Java SE5中,大量的类都是Iterable类型,次要包含所有的Collection类(然而不包含各种Map)。
foreach语句能够用于数组或其余任何Iterable,然而这并不意味着数组必定也是一个Iterable,’而任何主动包装也不会主动产生:
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
*/
}
}
复制代码
尝试把数组当做一个Iterable参数传递会导致失败。这阐明不存在任何从数组到Iterable的主动转换,你必须手动执行这种转换。
适配器办法习用法
如果现有一个Iterable类,你想要增加一种或多种在foreach语句中应用这个类的办法,应该怎么做呢?例如,假如你心愿能够抉择以向前的方向或是向后的方向迭代一个单词列表。如果间接继承这个类,并笼罩iterator()你只能替换现有的办法,而不能实现抉择。 一种解决方案是所谓适配器办法的习用法。“适配器”局部来自于设计模式,因为你必须提供特定接口以满足foreach语句。当你有一个接口并须要另一个接口时,编写适配器就能够解决问题。这里,我心愿在默认的前向迭代器的根底上,增加产生反向迭代器的能力,因而我不能应用笼罩,而是增加了一个可能产生Iterable对象的办法,该对象能够用于foreach语句。正如你所见,这使得咱们能够提供多种应用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(){
return new Iterable<T>() {
@Override
public Iterator<T> iterator() {
return new Iterator<T>(){
int cursor = size() - 1;
@Override
public boolean hasNext() {
return cursor > -1;
}
@Override
public T next() {
return get(cursor--);
}
};
}
};
};
}
复制代码
如果间接将ral对象置于foreach语句中,将失去(默认的)前向迭代器。然而如果在该对象上调用reversed()办法,就会产生不同的行为。 通过应用这种形式,我能够在IterableClass.java示例中增加两种适配器办法:
package p10;
import java.util.*;
public class MultiIterableClass extends IterableClass{
public Iterable<String> reversed(){
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
return new Iterator<String>() {
int cursor = words.length - 1;
@Override
public boolean hasNext() {
return cursor > -1;
}
@Override
public String next() {
return words[cursor--];
}
};
}
};
}
public Iterable<String> randomized(){
return new Iterable<String>() {
@Override
public Iterator<String> iterator() {
List<String> shuffled = new ArrayList<>(Arrays.asList(words));
Collections.shuffle(shuffled,new Random(47));
return shuffled.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.
*/
}
}
复制代码
留神,第二个办法random()没有创立它本人的Iterator,而是间接返回被打乱的List中的Iterator。 从输入中能够看到,Collection.shuffe()办法没有影响到原来的数组,而只是打乱了shuffied中的援用。之所以这样,只是因为randomized()办法用一个ArrayList将Arrays.asList()办法的后果包装了起来。如果这个由Arrays.asList()办法产生的List被间接打乱,那么它就会批改底层的数组,就像上面这样:
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]
*/
}
}
复制代码
在第一种状况中,Arrays.asList()的输入被传递给了ArrayList()的结构器,这将创立一个援用ia的元素的ArrayList,因而打乱这些援用不会批改该数组。然而,如果间接应用Arrays.asList(ia)的后果,这种打乱就会批改ia的程序。意识到Arrays.asList()产生的List对象会应用底层数组作为其物理实现是很重要的。只有你执行的操作会批改这个List,并且你不想原来的数组被批改,那么你就应该在另一个容器中创立一个正本。
参考: 《2020最新Java根底精讲视频教程和学习路线!》
链接:https://juejin.cn/post/693821…
发表回复