在数学中我们有集合的概念,所谓的一个集合,就是将数个对象归类而分成为一个或数个形态各异的大小整体。 一般来讲,集合是具有某种特性的事物的整体,或是一些确认对象的汇集。构成集合的事物或对象称作元素或是成员。集合具有:无序性、互异性、确定性。
而在我们计算机科学种集合的定义是:集合是一组可变数量的数据项(也可能是0个)的组合,这些数据项可能共享某些特征,需要以某种操作方式一起进行操作。一般来讲,这些数据项的类型是相同的,或基类相同(若使用的语言支持继承)。列表(或数组)通常不被认为是集合,因为其大小固定,但事实上它常常在实现中作为某些形式的集合使用。
——参考自*
通过上面的描述,我们很清楚的发现Java中容器存在的意义。在平常的生活中我们也会经常用到集合的概念来处理问题,自然而然我们用编程解决实际生活当中的问题也就要将集合用到我们的编程中。
在Java编程中,当我们想要持有一组类型相同或者基类相同的对象,我们不可能去分别持有每个对象的引用,那将是一件恐怖的事情。我们的解决办法就是将这组对象放在一个集合当中,也许有的人会说,那我们直接使用数组就可以了啊,在上面描述中我们也提到了数组的局限性,就是它的大小固定,而我们的应用经常需要的是动态的扩展集合大小。(当然一方面Java容器类库中的底层也用到了数组加以实现,另一方面数组也有其高效便捷的特性)而Java为我们做了更进一步的封装,容器类库使我们更加可以使用集合或说面向对象的观念去解决我们实际碰到的问题,java中的容器类库基本可以说是我们平时编程使用最频繁的类库。
下面我们首先从宏观整体的角度观摩下Java中容器类库的层次结构:
一、容器类的层次结构
简单的容器分类
——摘自 《Thinking in java》第17章图
(图的解释:点线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示某个类可以生成箭头所指向类的对象)
从图中我们可以很清晰的发现容器类库从基类角度分析可以分为两个模块:Collection、Map
1)Collection:一个独立元素的序列,这些元素都服从一条或多条规则。(注:Collection其实就是将一组数据对象按照一维线性的方式组织起来)List必须按照插入的顺序保存元素,而set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map:一组成对的“键值对”对象,允许你使用键来查找值。(注:Map其实是将键与值形成的二元组按照一维线性的方式组织起来,这里值得注意的是值可以使一个Collection或者Map,即嵌套结构,如:Map<Integer,List<String>>,Map<String,Map<String>>)从另一个角度来考虑Map,其实Map相当于ArrayList或者更简单的数组的一种扩展、推广。在数组中我们可以利用下标即数字访问数组当中的不同元素,那么数字与对象之间形成了一种关联,那么如果将这个数字的概念扩展成为对象,那同样的我们可以将对象与对象之间关联起来。即Map,也称为映射表、关联数组、字典允许我们使用一个对象来查找某个对象。
二、容器与泛型
我们有了容器库,相当于有了木桶,下面要往里面放入对象,可是这个对象并不是确定的,只有针对具体的编程环境才会有具体的应用,就像木桶做出来你并不知道它用来盛放什么(可以是水,沙子,油等等)。这就不得不说一个和容器类库紧密相关的概念泛型(泛型可参见【Java心得总结三】Java泛型上——初识泛型和【Java心得总结四】Java泛型下——万恶的擦除)
简单的说,Java在设计容器类库的时候为我们留下了空间,即我们可以向容器类库中装入任何我们需要的对象(当然了一个容器对象只能够装入一组相同继承源的对象)。比如
1 public class Container{ 2 public static void main(String[] args){ 3 List<Integer> li = new ArrayList<Integer>(); 4 List<String> ls = new ArrayList<String>(); 5 Map<Integer> mi = new HashMap<Integer>(); 6 Map<String> ms = new HashMap<String>(); 7 } 8 }
上面我分别用Collection中的List和Map做了示例,很明显我们可以看出利用泛型我们可以向容器中放入任何我们需要的类型,甚至可以使自己创建的类(当然有时需要我们实现一些接口,具体我将在后续博文中进行总结)
三、Foreach与迭代器
1)初识迭代器
上面那张图中我们简单讨论了Collection和Map,还有一个部分就是Iterator,即迭代器。
相信熟悉面向对象编程的朋友对foreach一定不会陌生,foreach语句可以更好地支持我们对于集合的遍历操作。
1 import java.util.*; 2 public class ForEachCollections { 3 public static void main(String[] args) { 4 Collection<String> cs = new LinkedList<String>(); 5 Collections.addAll(cs, 6 "Take the long way home".split(" ")); 7 for(String s : cs) 8 System.out.print("‘" + s + "‘ "); 9 } 10 } /* Output: 11 ‘Take’ ‘the’ ‘long’ ‘way’ ‘home’
上面的代码cs是一个Collection,这也说明了foreach能够与所有Cellection对象一起工作(包括List,Set,Queue),另外需要注意的是Java中对foreach的操作关键字仍然是for而不像C#中关键字直接变为了foreach。
这里我们会有一个疑问,foreach是怎么做到在集合中移动从而达到遍历的效果呢?(因为像如果我们用普通的for循环如:for(int i = 0; i < 10; i++){},其中i就是我们一直在移动的变量,从而达到了遍历数组的效果。)
答案是Iterable接口,该接口包含一个能够产生Iterator的iterator()方法,foreach就是使用Iterator在序列中移动的。因此如果你创建了任何实现了Iterable的类,都可以将它用于foreach语句中(着我们就看出了为什么Iterator接口会出现在上图中了,因为所有的Collection类都实现了Iterator接口,它们都可以产生迭代器供foreach使用,特别注意Map并没有实现Iterator接口我们需要利用别的方式来遍历Map,后续)
2)创建实现Iterable接口的类
1 import java.util.*; 2 public class IterableClass implements Iterable<String> { 3 protected String[] words = ("And that is how " + 4 "we know the Earth to be banana-shaped.").split(" "); 5 public Iterator<String> iterator() { 6 return new Iterator<String>() { 7 private int index = 0; 8 public boolean hasNext() { 9 return index < words.length; 10 } 11 public String next() { 12 return words[index++]; 13 } 14 public void remove() { // Not implemented 15 throw new UnsupportedOperationException(); 16 } 17 }; 18 } 19 public static void main(String[] args) { 20 for(String s : new IterableClass()) 21 System.out.print(s + " "); 22 } 23 } /* Output: 24 And that is how we know the Earth to be banana-shaped. 25 *///:~
上面的例子我们在自己创建的类IterableClass中实现了Iterator<String>接口,并且通过Iterator()方法返回的是实现了Iterator<String>的匿名内部类的实例,该匿名内部类可以遍历数组中的所有单词。这样在main中我们将IterableClass应用于了foreach语句。(关于匿名内部类,博文后续)
3)适配Collection的迭代器接口
针对我们自己创建的类我们可以通过实现Iterable接口来完成我们自己需要的遍历操作。然而如果我们利用foreach操作遍历Java类库中提供的Collection时,就会限制我们的遍历操作,因为如Java在ArrayList中只提供了从前往后的顺序遍历迭代器,假设我想要逆序遍历,那怎么办呢?
利用适配器方法来实现:
1 import java.util.*; 2 class ReversibleArrayList<T> extends ArrayList<T> { 3 public ReversibleArrayList(Collection<T> c) { super(c); } 4 public Iterable<T> reversed() { 5 return new Iterable<T>() { 6 public Iterator<T> iterator() { 7 return new Iterator<T>() { 8 int current = size() - 1; 9 public boolean hasNext() { return current > -1; } 10 public T next() { return get(current--); } 11 public void remove() { // Not implemented 12 throw new UnsupportedOperationException(); 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 *///:~
在这段代码中我们声明了ReversibleArrayList<T>类用来做我们的适配器(适配器设计模式),这里我们继承自ArrayList<T>类(Collection类库已有类),并且在此基础上扩展了一个reversed()方法,它会返回一个逆序遍历数组的迭代器,从而达到我们的目的。在main函数中我们看到了在代码第29行我们调用了我们扩展的reversed()方法返回了不同的迭代器,达到了逆序遍历的效果。
总结:
这篇博文从宏观整体的角度把握了java类库为我们提供的容器类库,并且也详细阐明了在实际中对迭代器的使用方式。在后续两篇博文中,我会分别整理Collection和Map(请参见博文【Java心得总结五】Java容器中——Collection和【Java心得总结五】Java容器下——Map)
参考——《Java编程思想第4版》