读Java编程思想随笔の持有对象
如果一个程序只包含固定数量的且生命周期都是已知的对象,那么这是一个非常简单的程序。
通常,程序总是根据运行时才知道的某些条件去创建新对象。在此之前,不会知道所需对象的数量,甚至不知道确切的类型。为解决这个普遍的编程问题,需要在任意时刻任意位置创建任意数量的对象。所以,就不能依靠创建命名的引用来持有每一个对象:
MyType aReference;
因为你不知道实际上会需要多少这样的引用。
大多数语言都提供某种方法来解决这个基本问题。Java有多种方式保存对象。例如数组,它是编译器支持的类型。数组是保存一组对象的最有效的方式,如果你想保存一组基本类型数据,也推荐使用这种方式。但是数组具有固定的尺寸,而在更一般情况中,你在写程序时并不知道将需要多少对象,或者是否需要更复杂的方式存储对象,因此数组尺寸固定这一限制显得过于受限了。
Java实用类库还提供了一套相当完整的容器类来解决这个问题,其中基本的类型是list\set\queue和map。这些对象类型也称为集合类。容器提供了完整的方法来保存对象,你可以使用这些工具来解决数量惊人的问题。
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1、collection。一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序。
2、map。一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在一起。映射表允许我们使用另一个对象来查找某个对象,它也被称为关联数组,因为它将某些对象与另外一些对象关联在一起;或者被称为字典,因为你可以使用键对象来查找值对象。
尽管并非总是这样,但是在理想情况下,你编写的大部分代码都是在与这些接口打交道,并且你唯一需要指定所使用的精确类型的地方就是在创建的时候。因此,你可以像下面这样创建一个list:
List<Apple> apples = new ArrayList<Apple>();
使用接口的目的在于如果你决定去修改你的实现,你所需的只是在创建初修改它,就像这样:
List<Apple> apples = new LinkedList<Apple>();
因此,你应该创建一个具体类的对象,将其转型为对应接口,然后在其余的代码中都使用这个接口。这种方式并非总能奏效,因为某些类具有额外的功能,例如,LinkedList具有List没有包含的额外的方法,而TreeMap中也具有map接口中未包含的方法。如果你需要使用这些方法,你就不能将它们向上转型为更通用的接口。
1 public class SimpleCollection { 2 public static void main(String[] args) { 3 Collection<Integer> c = new ArrayList<Integer>(); 4 for (int i = 0; i<10; i++){ 5 c.add(i); 6 } 7 for (Integer i : c){ 8 System.out.println(i+","); 9 } 10 } 11 }
在Java util包中Arrays和Collections类中都有很多实用的方法,可以在一个Collection中添加一组元素。Arrays.asList()方法接受一组或是一个用逗号隔开的元素列表(使用可变参数),并将其转换为一个List对象。Collections.addAll()方法接受一个collection对象,以及一个数组或是一个用逗号分隔的列表,将元素添加到collection中。
1 public class AddingGroups { 2 public static void main(String[] args) { 3 Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1,2,3,4,5));//Arrays.asList()接受一个数组或一个用逗号分隔的元素列表,并将其转换为一个list对象 4 Integer moreInts [] = {6,7,8,9,10}; 5 collection.addAll(Arrays.asList(moreInts));//Arrays.asList()接受一个数组或一个用逗号分隔的元素列表,并将其转换为一个list对象 6 Collections.addAll(collection,11,12,13,14,15);//Collections.addAll()接受一个Collection对象,以及一个数组或一个用逗号分隔的元素列表 7 System.out.println(collection.size()); 8 Collections.addAll(collection,moreInts);//Collections.addAll()接受一个Collection对象,以及一个数组或一个用逗号分隔的元素列表 9 System.out.println(collection.size()); 10 List<Integer> list = Arrays.asList(16,17,18,19,20);//这种情况下,底层表示的是数组,因此不能调整尺寸,如果你试图在这种列表中添加或删除元素,就可能引发改变数组尺寸的尝试。 11 list.set(1,99); 12 //list.add(21);// java.lang.UnsupportedOperationException 13 } 14 }
Collections是一个包装类,此类不能实例化,就像一个工具类;
Collection是一个接口,下面含有诸如list\set等子接口。
容器打印
你必须使用Arrays.toString()来产生数组的可打印表示,但是打印容器无需任何帮助。
1 public class PrintingContainers { 2 static Collection fill(Collection<String> collection){ 3 collection.add("rat"); 4 collection.add("cat"); 5 collection.add("aat"); 6 collection.add("dog"); 7 collection.add("dog"); 8 return collection; 9 } 10 static Map fill(Map<String,String> map){ 11 map.put("rat","ruizi"); 12 map.put("cat","dingdang"); 13 map.put("dig","dingdang"); 14 map.put("dog","ahuang"); 15 map.put("dog","shinubi"); 16 return map; 17 } 18 19 public static void main(String[] args) { 20 //PrintingContainers pc = new PrintingContainers(); 21 System.out.println(fill(new ArrayList<String>())); 22 System.out.println(fill(new LinkedList<String>())); 23 System.out.println(fill(new HashSet<String>())); 24 System.out.println(fill(new HashMap<String, String>())); 25 System.out.println(fill(new TreeMap<String, String>()));//按照比较结果的升序保存键 26 System.out.println(fill(new TreeSet<String>()));//有顺序 27 System.out.println(fill(new LinkedHashMap<String, String>()));//按照插入顺序保存键 28 System.out.println(fill(new LinkedHashSet<String>()));//有顺序 29 } 30 } 31 // [rat, cat, aat, dog, dog] 32 // [rat, cat, aat, dog, dog] 33 // [cat, aat, dog, rat] 34 // {cat=dingdang, dig=dingdang, dog=shinubi, rat=ruizi} 35 // {cat=dingdang, dig=dingdang, dog=shinubi, rat=ruizi} 36 // [aat, cat, dog, rat] 37 // {rat=ruizi, cat=dingdang, dig=dingdang, dog=shinubi} 38 // [rat, cat, aat, dog]
这里展示了Java容器类库中的两种主要类型,它们的区别在于容器中每个槽保存的元素个数。 Collection在每个槽中只能保存一个元素。此类容器包括:List,它以特定的顺序保存一组元素;Set,元素不能重复;Queue,只允许在容器的一端插入元素,并从另外一端移除元素。map在每个槽内保存了两个对象,即键与相对应的值。
查看输出会发现,默认的打印行为可生成可读性很好的结果。
ArrayList与LinkedList都是list类型,从输出可以看出,它们都按照被插入的顺序保存元素。两者的不同之处不仅在于执行某些类型时的操作性能,而且LinkedList包含的操作也多于ArrayList。
HashSet、TreeSet、LinkedHashSet都是Set类型,输出显示在Set中,每个相同的项只有保存一次,但是输出也显示了不同的Set实现存储元素的方式也不同。HashSet使用的是相当复杂的方式来存储元素的,这种技术是最快的获取元素的方式,因此,存储的顺序看起来并无实际意义。如果存储顺序很重要,那么可以使用TreeSet,它按照比较结果的升序保存对象;或者使用LinkedHashSet,它按照被添加的顺序保存对象。
HashMap、TreeMap和LinkedHashMap;与HashSet一样,HashMap也提供了最快的查找技术,也没有按照任何明显的顺序来保存元素。TreeMap按照比较结果升序来保存键,LinkedHashMap按照插入顺序保存键,同时还保留了HashMap的查询速度。
迭代器
任何容器类,都必须有某种方式可以插入元素并将它们再次取回。毕竟,持有事物是容器最基本工作。如果从更高层角度去思考,会发现这里有个缺点:要使用容器,必须对容器确切类型编程。初看起来这没有什么不好,但是考虑下面的情况:如果原本是对着List编码的,但是后来发现如果能把相同代码应用于Set中,将会显得非常方便,此时应该怎么做?或者从头开始编写通用代码,它们只是使用容器,不知道或者不关心容器的类型,那么如何才可以不编写代码就可以应用于不同类型的容器呢?
迭代器的概念可以用于达成此目的。迭代器是一个对象,它的工总是遍历并选择序列中的对象,而客户端程序员不必知道或关心序列的底层结构。迭代器通常被称为轻量级对象:创建它的代价小。
1 public class SimpleIteration { 2 public static void main(String[] args) { 3 List<String> pets = new ArrayList<String>(); 4 pets.add("dog"); 5 pets.add("pig"); 6 pets.add("cat"); 7 Iterator<String> it = pets.iterator(); 8 while(it.hasNext()){ 9 String pet = it.next(); 10 System.out.println("pet:"+pet); 11 } 12 System.out.println(); 13 for (String s : pets){ 14 System.out.println("pet:"+s); 15 } 16 System.out.println(); 17 it = pets.iterator(); 18 for (int i = 0;i<1;i++){ 19 it.next(); 20 it.remove(); 21 } 22 //不幸的把第一个元素剔除了 23 System.out.println(pets); 24 } 25 }
接受对象容器并传递它,从而在每个对象上都执行操作,这种思想十分强大:
1 public class CrossContainerIteration { 2 public static void display(Iterator<String> it){ 3 while(it.hasNext()){ 4 String pet = it.next(); 5 System.out.println("pet:"+pet); 6 } 7 System.out.println(); 8 } 9 10 public static void main(String[] args) { 11 ArrayList<String> pets = new ArrayList<String>(); 12 LinkedList<String> petsll = new LinkedList<String>(); 13 HashSet<String> peths = new HashSet<String>(); 14 TreeSet<String> petts = new TreeSet<String>(); 15 display(pets.iterator()); 16 display(petsll.iterator()); 17 display(peths.iterator()); 18 display(petts.iterator()); 19 } 20 }
ListIterator
ListIterator是一个更加强大的Iterator子类型,它只能用于各种list类的访问,尽管Iterator只能向前移动,但是ListIterator可以双向移动。它还可以产生相对于迭代器在列表中指向的当前位置的前一个和后一个元素索引,并且可以使用set()方法替换它访问过的最后一个元素。你可以通过调用istIterator方法产生一个指向list开始处的ListIterator,并且还可以通过调用listIterator(n)方法创建一个一开始就指向列表索引为n的元素出的ListIterator。
1 public class ListIteration { 2 public static void main(String[] args) { 3 List<String> pets = new ArrayList<String>(); 4 pets.add("dog"); 5 pets.add("pig"); 6 pets.add("cat"); 7 ListIterator lit = pets.listIterator(); 8 //指针往后走 9 while(lit.hasNext()){ 10 System.out.println(lit.next()+"\t"+lit.nextIndex()+"\t"+lit.previousIndex()); 11 } 12 System.out.println(); 13 //指针往回走 14 while(lit.hasPrevious()){ 15 System.out.println(lit.previous()); 16 } 17 System.out.println(); 18 System.out.println(pets); 19 lit = pets.listIterator(); 20 while(lit.hasNext()){ 21 lit.next(); 22 lit.set("duck"); 23 } 24 System.out.println(pets); 25 } 26 }
ListIterator
LinkedList也像arraylist一样实现了基本的list接口,但是它执行某些操作,比如说在list的中间插入或移除时比arraylist更加高效,而在随机访问操作方面却要损色一些LinkedList还添加了使其可以作用在栈、队列或双端队列的方法。
1 public class LinkedListFeatures { 2 public static void main(String[] args) { 3 LinkedList<String> pets = new LinkedList<String>(); 4 pets.add("rat"); 5 pets.add("manx"); 6 pets.add("cymric"); 7 pets.add("mutt"); 8 pets.add("pug"); 9 System.out.println(pets); 10 System.out.println("pets.getfirst:"+pets.getFirst()); 11 System.out.println("pets.element:"+pets.element()); 12 System.out.println("pets.peek:"+pets.peek()); 13 System.out.println("pets.remove:"+pets.remove()); 14 System.out.println("pets.removefirst:"+pets.removeFirst()); 15 System.out.println("pets.poll():"+pets.poll()); 16 System.out.println(pets); 17 pets.addFirst("pig"); 18 System.out.println("after addfirst:"+pets); 19 pets.offer("duck"); 20 System.out.println("after offer:"+pets); 21 pets.add("duck"); 22 System.out.println("after add:"+pets); 23 pets.addLast("pandan"); 24 System.out.println("after addLast:"+pets); 25 System.out.println("pets.removeLast:"+pets.removeLast()); 26 } 27 28 } 29 // [rat, manx, cymric, mutt, pug] 30 // pets.getfirst:rat 31 // pets.element:rat 32 // pets.peek:rat 33 // pets.remove:rat 34 // pets.removefirst:manx 35 // pets.poll():cymric 36 // [mutt, pug] 37 // after addfirst:[pig, mutt, pug] 38 // after offer:[pig, mutt, pug, duck] 39 // after add:[pig, mutt, pug, duck, duck] 40 // after addLast:[pig, mutt, pug, duck, duck, pandan] 41 // pets.removeLast:pandan
Stack
栈只是一种概念或一种结构,栈通常是指后进先出(LIFO)的容器。有时栈也被成为叠加栈,因为最后“压入”栈的元素,第一个“弹出”栈,LinkedList具有能够直接实现栈的所有功能的方法,因此可以直接将LinkedList作为栈来使用。
1 public class Stack<T> { 2 private LinkedList<T> storage = new LinkedList<T>(); 3 public void push(T t){storage.addFirst(t);} 4 public T peek(){return storage.getFirst();} 5 public T pop(){return storage.removeFirst();} 6 public boolean empty(){return storage.isEmpty();} 7 public String toString(){return storage.toString();} 8 }
这里通过使用泛型,引入了在栈的类定义中最简单的可行示例。类名之后的<T>告诉编译器将是一个参数化类型,而其中的类型参数,即在类被使用时将会被实际类型替换的参数,就是T。大体上,这个类是在声明“我们在定义一个可以持有T类型对象的stack。”stack是用LinkedList实现的,而LinkedList也会被告知它持有T类型的对象,而peek()和pop()将返回T类型对象。peek()方法将提供栈顶元素,但是并不将其从栈顶移除,而pop()将移除并返回栈顶元素。
1 public class StackTest { 2 public static void main(String[] args) { 3 Stack<String> stack = new Stack<String>(); 4 for (String s:"My dog has fleas".split(" ")){stack.push(s);} 5 while(!stack.empty()){ 6 System.out.print(stack.pop() + " "); 7 } 8 } 9 }
Set
set不保存重复的元素(至于如何判断元素相同则较为复杂,稍后便会看到)。如果你试图将相同对象的多个实例添加到set中,那么它就会阻止这种重复现象 。set中最常使用的是测试归属性,你可以很容易地询问某个对象是否存在set中。正因为如此,查找就成为了set中 最重要的操作。set具有与collection完全一样的接口,因此没有额外的功能。不像前面有两个不同的list。实际上set就是collection,只是行为不同(实现不同)。
1 public class SetOfInteger { 2 public static void main(String[] args) { 3 Random rand = new Random(47); 4 Set<Integer> intset = new HashSet<Integer>(); 5 for (int i = 0 ; i<10000;i++){ 6 intset.add(rand.nextInt(30)); 7 } 8 System.out.println(intset); 9 } 10 } 11 //[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 17, 16, 19, 18, 21, 20, 23, 22, 25, 24, 27, 26, 29, 28]
输出的顺序没有任何规律可循,这是因为出于速度原因考虑,HashSet使用了散列。HashSet所维护的顺序与TreeSet或LinkedHashSet都不同,因为它们的实现具有不同的元素存储方式,TreeSet将元素存储在红-黑数据结构中(按照比较结果的升序保存对象),而HashSet使用了散列函数。LinkedHashSet因为查询速度的原因也使用了散列,但是看起来它使用了链表来维护元素的插入顺序。
如果你想对结果排序,一种方式是使用TreeSet来代替HashSet。
1 public class SortedSetOfInteger { 2 public static void main(String[] args) { 3 Random rand = new Random(47); 4 SortedSet<Integer> intset = new TreeSet<Integer>(); 5 for (int i = 0 ; i<10000;i++){ 6 intset.add(rand.nextInt(30)); 7 } 8 System.out.println(intset); 9 } 10 }
使用contains()测试set归属性
1 public class SetOperations { 2 public static void main(String[] args) { 3 Set<String> set = new HashSet<String>(); 4 Collections.addAll(set, "A B C D E F G H I J K L".split(" ")); 5 set.add("M"); 6 System.out.println("H:" + set.contains("H")); 7 System.out.println("N:" + set.contains("N")); 8 9 Set<String> set_ = new HashSet<String>(); 10 Collections.addAll(set_, "H I J K L".split(" ")); 11 12 System.out.println("set_ in set:" + set.containsAll(set_)); 13 set.remove("H"); 14 System.out.println("set:" + set); 15 System.out.println("set_ in set:" + set.containsAll(set_)); 16 set.removeAll(set_); 17 System.out.println("set_ removed from set:" + set); 18 Collections.addAll(set, "X Y Z".split(" ")); 19 System.out.println("'X Y Z' added to set:"+set); 20 } 21 } 22 /* 23 H:true 24 N:false 25 set_ in set:true 26 set:[D, E, F, G, A, B, C, L, M, I, J, K] 27 set_ in set:false 28 set_ removed from set:[D, E, F, G, A, B, C, M] 29 'X Y Z' added to set:[D, E, F, G, A, B, C, M, Y, X, Z]*/
Map
1 public class Statistics { 2 public static void main(String[] args) { 3 Random rand = new Random(47); 4 Map<Integer,Integer> m = new HashMap<Integer,Integer>(); 5 for (int i = 0;i<10000;i++){ 6 int r = rand.nextInt(20); 7 Integer freq = m.get(r); 8 m.put(r,freq==null?1:freq+1); 9 } 10 System.out.println(m); 11 } 12 }
Queue
队列是一个典型的先进先出的容器。即从容器的一段放入事物,从另一端取出,并且事物放入容器的顺序与事物取出容器的顺序是相同的,队列常被当做一个可靠的将对象从程序的某个区域传输到另一个区域的途径。队列在并发编程中特别重要,因为它们可以安全的将对象从一个任务,传输给另一个任务。
public class QueueDemo { public static void main(String[] args) { Queue<Integer> queue = new LinkedList<Integer>(); Random rand = new Random(47); for (int i =0;i<10;i++){ queue.offer(rand.nextInt(i+10)); } printQ(queue); Queue<Character> qc = new LinkedList<Character>(); for (char c:"Brontosaurus".toCharArray()){ qc.offer(c); } printQ(qc); } public static void printQ(Queue q){ while (q.peek()!=null){ System.out.print(q.remove()+" "); } System.out.println(); } }
队列优先级 PriorityQueue
1 public class PriorityQueueDemo { 2 public static void main(String[] args) { 3 PriorityQueue<Integer> priorityQueue = new PriorityQueue<Integer>(); 4 Random rand = new Random(47); 5 for (int i=0;i<10;i++){ 6 priorityQueue.offer(rand.nextInt(i+10)); 7 } 8 QueueDemo.printQ(priorityQueue); 9 List<Integer> ints = Arrays.asList(25,22,20,18,14,21,23,25); 10 priorityQueue = new PriorityQueue<Integer>(ints); 11 QueueDemo.printQ(priorityQueue); 12 priorityQueue = new PriorityQueue<Integer>(ints.size(), Collections.reverseOrder()); 13 priorityQueue.addAll(ints); 14 QueueDemo.printQ(priorityQueue); 15 16 String fact = "EDUCATION SHOULD ESCHEW OBFUSCATION"; 17 List<String> strings = Arrays.asList(fact.split("")); 18 PriorityQueue<String> stringPQ = new PriorityQueue<String>(strings); 19 QueueDemo.printQ(stringPQ); 20 21 Set<Character> charSet = new HashSet<Character>(); 22 for (char c:fact.toCharArray()){ 23 charSet.add(c); 24 } 25 PriorityQueue<Character> characterPQ = new PriorityQueue<Character>(charSet); 26 QueueDemo.printQ(characterPQ); 27 } 28 }
Collection与Iterator
Collection是描述所有序列容器的共性的根接口,它可能会被认为是一个"附属接口",即因为要表示其他若干个接口的共性而出现的接口。另外,Java.util.AbstractCollection类提供了Collection默认实现,使得你可以创建AbstractCollection的子类型,而其中没有不必要的代码重复。
使用接口描述的一个理由是它可以使我们创建更通用的代码。通过针对接口而非具体实现来编写代码,我们的对象可以应用更多的对象类型。因此,我编写的方法将接受一个Collection,那么该方法就可以应用于任何实现了Collection的类,这也就使得一个新类可以选择去实现Collection接口,以便我的方法可以使用它。在Java中,遵循C++的方式看起来似乎很明智,即用迭代器而不是Collection来表示容器之间的共性。但是,这两种方法绑定到了一起,因为实现Collection接口就意味着需要提供iterator()方法。
1 public class InterfaceVSIterator { 2 public static void display(Iterator<Pet> it){ 3 while(it.hasNext()){ 4 Pet p = it.next(); 5 System.out.print(p.id() + "\t" + p); 6 } 7 System.out.println(); 8 } 9 public static void display(Collection<Pet> pets){ 10 for(Pet pet:pets){ 11 System.out.print(pet.id() +"\t"+pet); 12 } 13 System.out.println(); 14 } 15 16 public static void main(String[] args) { 17 List<Pet> petList = Pets.arrayList(8); 18 Set<Pet> petSet = new HashSet<Pet>(petList); 19 Map<String,Pet> petMap = new LinkedHashMap<String, Pet>(); 20 String [] names = ("Ralph,Eric,Robin,Lacey".split(",")); 21 for (int i=0;i<names.length;i++){ 22 petMap.put(names[i],petList.get(i)); 23 } 24 display(petList); 25 display(petList.iterator()); 26 display(petSet); 27 display(petSet.iterator()); 28 System.out.println(petMap); 29 System.out.println(petMap.keySet()); 30 display(petMap.values()); 31 display(petMap.values().iterator()); 32 } 33 } 34 // 0 Mutt1 Mutt2 Mutt3 Mutt4 Mutt5 Mutt6 Mutt7 Mutt 35 // 0 Mutt1 Mutt2 Mutt3 Mutt4 Mutt5 Mutt6 Mutt7 Mutt 36 // 2 Mutt1 Mutt0 Mutt6 Mutt5 Mutt4 Mutt3 Mutt7 Mutt 37 // 2 Mutt1 Mutt0 Mutt6 Mutt5 Mutt4 Mutt3 Mutt7 Mutt 38 // {Ralph=Mutt, Eric=Mutt, Robin=Mutt, Lacey=Mutt} 39 // [Ralph, Eric, Robin, Lacey] 40 // 0 Mutt1 Mutt2 Mutt3 Mutt 41 // 0 Mutt1 Mutt2 Mutt3 Mutt
Foreach与迭代器
到目前为止,foreach语法主要用于数组,但是它也可以应用于任何Collection对象。实际上已经看到很多使用ArrayList时用到它的示例
1 public class ForEachCollections { 2 public static void main(String[] args) { 3 Collection<String> cs = new LinkedList<String>(); 4 Collections.addAll(cs, 5 "Take the long way home".split(" ")); 6 for(String s : cs) 7 System.out.print("'" + s + "' "); 8 } 9 } /* Output: 10 'Take' 'the' 'long' 'way' 'home' 11 *///:~
由于cs是一个Collection,所以这段代码展示了能够与foreach一起工作室所有Collection对象的特性。
之所以能够工作,是因为Java SE5引入了新的被称为Iterable的接口,该接口包含一个能够产生Iterator的iterator()方法,并且Iterator接口被foreach用来在序列中移动。因此如果你创建了任何实现Iterable的类,都可以将它放入foreach语句中
1 public class IterableClass implements Iterable<String> { 2 protected String[] words = ("And that is how " + 3 "we know the Earth to be banana-shaped.").split(" "); 4 public Iterator<String> iterator() { 5 return new Iterator<String>() { 6 private int index = 0; 7 public boolean hasNext() { 8 return index < words.length; 9 } 10 public String next() { return words[index++]; } 11 public void remove() { // Not implemented 12 throw new UnsupportedOperationException(); 13 } 14 }; 15 } 16 public static void main(String[] args) { 17 for(String s : new IterableClass()) 18 System.out.print(s + " "); 19 } 20 } /* Output: 21 And that is how we know the Earth to be banana-shaped. 22 *///:~
iterator()方法返回的是实现了Iterator<String> 的匿名内部类的实例,该匿名内部类可以遍历数组中的所有单词。在main()中,你可以看到IterableClass确实可以用于foreach语句中。
在Java SE5中,大量的类都是Iterable类型,主要包括所有的Collection类。例如下面代码可以显示所有操作系统的环境变量。
1 public class EnvironmentVariables { 2 public static void main(String[] args) { 3 for(Map.Entry entry: System.getenv().entrySet()) { 4 System.out.println(entry.getKey() + ": " + 5 entry.getValue()); 6 } 7 } 8 } /* (Execute to see output) *///:~
foreach语句可以用于数组或其他任何Iterable,但是这并不意味着数组肯定也是一个Iterable,而任何自动包装也不会自动发生
1 public class ArrayIsNotIterable { 2 static <T> void test(Iterable<T> ib) { 3 for(T t : ib) 4 System.out.print(t + " "); 5 } 6 public static void main(String[] args) { 7 test(Arrays.asList(1, 2, 3)); 8 String[] strings = { "A", "B", "C" }; 9 // An array works in foreach, but it's not Iterable: 10 //! test(strings); 11 // You must explicitly convert it to an Iterable: 12 test(Arrays.asList(strings)); 13 } 14 } /* Output: 15 1 2 3 A B C 16 *///:~
尝试把数组当作一个Iterable参数传递会导致失败。这说明不存在任何从数组到Iterable的自动转换,你必须手工执行这种转换。
适配器方法惯用法
如果现有一个Iterable类,你想要添加一种或多种在foreach语句中使用这个类的方法,应该怎么做?例如,假设你希望可以选择向前或向后迭代一个单词列表。如果直接继承这个类,并覆盖iterator()方法,你只能替换现有的方法,而不能实现选择。
一种解决办法是所谓适配器方法的惯用法。适配器部分来自于设计模式,因为你必须提供特定接口以满足foreach语句。当你有一个接口并需要另一个接口时,编写适配器就可以解决问题。这里,我希望在默认的前向迭代器的基础上,添加产生反向迭代器的能力,因此我不能使用覆盖,而是添加了一个能够产生Iterable对象的方法,该对象可以用于foreach语句。
1 class ReversibleArrayList<T> extends ArrayList<T> { 2 public ReversibleArrayList(Collection<T> c) { super(c); } 3 public Iterable<T> reversed() { 4 return new Iterable<T>() { 5 public Iterator<T> iterator() { 6 return new Iterator<T>() { 7 int current = size() - 1; 8 public boolean hasNext() { return current > -1; } 9 public T next() { return get(current--); } 10 public void remove() { // Not implemented 11 throw new UnsupportedOperationException(); 12 } 13 }; 14 } 15 }; 16 } 17 } 18 19 public class AdapterMethodIdiom { 20 public static void main(String[] args) { 21 ReversibleArrayList<String> ral = 22 new ReversibleArrayList<String>( 23 Arrays.asList("To be or not to be".split(" "))); 24 // Grabs the ordinary iterator via iterator(): 25 for(String s : ral) 26 System.out.print(s + " "); 27 System.out.println(); 28 // Hand it the Iterable of your choice 29 for(String s : ral.reversed()) 30 System.out.print(s + " "); 31 } 32 } /* Output: 33 To be or not to be 34 be to not or be To 35 *///:~
如果直接将ral对象置于foreach语句中,将得到默认的前向迭代器。但是在该对象上调用reversed()方法,就会产生不同的行为。
通过使用这种方式,我可以在IterableClass.java示例中添加两种适配器方法
1 public class MultiIterableClass extends IterableClass { 2 public Iterable<String> reversed() { 3 return new Iterable<String>() { 4 public Iterator<String> iterator() { 5 return new Iterator<String>() { 6 int current = words.length - 1; 7 public boolean hasNext() { return current > -1; } 8 public String next() { return words[current--]; } 9 public void remove() { // Not implemented 10 throw new UnsupportedOperationException(); 11 } 12 }; 13 } 14 }; 15 } 16 public Iterable<String> randomized() { 17 return new Iterable<String>() { 18 public Iterator<String> iterator() { 19 List<String> shuffled = 20 new ArrayList<String>(Arrays.asList(words)); 21 Collections.shuffle(shuffled, new Random(47)); 22 return shuffled.iterator(); 23 } 24 }; 25 } 26 public static void main(String[] args) { 27 MultiIterableClass mic = new MultiIterableClass(); 28 for(String s : mic.reversed()) 29 System.out.print(s + " "); 30 System.out.println(); 31 for(String s : mic.randomized()) 32 System.out.print(s + " "); 33 System.out.println(); 34 for(String s : mic) 35 System.out.print(s + " "); 36 } 37 } /* Output: 38 banana-shaped. be to Earth the know we how is that And 39 is banana-shaped. Earth that how the be And we know to 40 And that is how we know the Earth to be banana-shaped. 41 *///:~