关于java:Foreach和迭代器

35次阅读

共计 5488 个字符,预计需要花费 14 分钟才能阅读完成。

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…

正文完
 0