1. 概述
通常,程序总是运行时才知道的根据某些条件去创建新对象。在此之前,不会知道所需对象的数量,甚至不知道确切的类型,为解决这个普遍的编程问题:需要在任意时刻和任意位置创建任意数量的对象,所以,就不能依靠创建命名的引用来持有每一个对象,因为你不知道实际上会需要多少这样的引用。
大多数语言都提供某种方法来解决这个基本问题。Java有多种方式保存对象(应该说是对象的引用)。例如数组,它是编译器支持的类型。数组是保存一组对象的最有效的方式,如果你想保存一组基本类型数据,也推荐使用这种方式。但是数据具有固定的尺寸,而在更一般的情况中,你在写程序时并不知道将需要多少个对象,或者是否需要更复杂的方式来存储对象,因此数组尺寸固定这一限制就显得过于受限了。
所以Java实用类库提供了一套相当完整的容器类来解决这个问题,其中基本的类型是List、Set、Queue和Map。这些对象类型也称为集合类,但由于Java的类库中使用了Collection这个名字来指代该类库的一个特殊子集,所以我使用了范围更广的术语“容器”称呼他们。
2. 基本概览
Java容器类类库的用途是“保存对象”,并将其划分为两个不同的概念:
1)Collection:一个独立元素的序列,这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素,而Set不能有重复元素。Queue按照排队规则来确定对象产生的顺序(通常与它们被插入的顺序相同)。
2)Map:一组成对的“键值对”对象,允许你使用键来查找值。ArrayList允许你使用数字来查找值,因此在某种意义上讲,它将数字与对象关联在一起。映射表允许我们使用另外一个对象来查找某个对象,它也被称为“关联数组”,因为它将某些对象与另外一些对象关联在了一起;或者被称为“字典”,因为你可以使用键对象来查找对象,就像在字典中使用单词来定义一样。
3. List
List承诺可以将元素维护在特定的序列中。List接口在 Collection的基础上添加了大量的方法,使得可以在List的中间插入和移除元素。
有两种类型的List:
1)基本的 Arraylist,它长于随机访问元素,但是在List的中间插入和移除元素时较慢。
2)LinkedList,它通过代价较低的在List中间进行插入和删除操作,提供了优化的顺序访问。 LinkedList在随机访问方面相对比较慢,但是它的特性集较 ArrayList更大。
4. 迭代器
迭代器(一种设计模式)的概念可以用于不重写代码就可以应用于不同类型的容器。迭代器是一个对象,它的工作是遍历并选择序列中的对象,而客户端程序员不必知道或关心该序列底层的结构。此外,选代器通常被称为轻量级对象:创建它的代价小。因此,经常可以见到对迭代器有些奇怪的限制
例如,Java的 Iterator只能单向移动,这个 Iterator只能用来:
1)使用方法 iterator()要求容器返回一个 Iterator。 Iterator将准备好返回序列的第一个元素。
2)使用next()获得序列中的下一个元素。
3)使用 hashNext()检查序列中是否还有元素。
4)使用 remove()将迭代器新近返回的元素删除。
5. LinkedList
LinkedLlist也像 ArrayListー样实现了基本的List接ロ,但是它执行某些操作(在List的中间插入和移除)时比 ArrayList更高效,但在随机访问操作方面却要逊色一些。
LinkedList还添加了可以使其用作栈、队列或双端队列的方法。
这些方法中有些彼此之间只是名称有些差异,或者只存在些许差异,以使得这些名字在特定用法的上下文环境中更加适用(特别是在 Queuer中)。例如, getFirst()和 element()完全一样,它们都返回列表的头(第一个元素),而并不移除它,如果List为空,则抛出 NoSuchElementException。peek()方法与这两个方式只是稍有差异,它在列表为空时返回null。
removeFirst()与remove()也是完全一样的,它们移除并返回列表的头,而在列表为空时抛出NoSucheElementException。
poll()稍有差异,它在列表为空时返回null。
addFirst()与add()和 addLast()相同,它们都将某个元素插入到列表的尾(端)部
removeLast()移除并返回列表的最后一个元素。
6. Stack
“栈”通常是指“后进先出”(LIFO)容器。有时栈也被称为叠加栈,因为最后“压入”栈的元素,第一个“弹出”栈。经常用来类比栈的事物是装有弹簧的储物器的自助餐托盘,最后装入的托盘总是最先拿出使用。
7. Set
Set不保存重复的元素。如果你试图将相同对象的多个实例添加到Set中,那么它就会阻止这种重复现象。Set中最常被使用的是测试归属性,你可以很容易地询问某个对象是否在某个Set中。正因如此,查找就成为了Set中最重要的操作,因此你通常都会选择一个 HashSet的实现,它专门对快速查找进行了优化。
Set具有与 Collection完全一样的接口,因此没有任何额外的功能,不像前面有两个不同的List。实际上Set就是 Collection,只是行为不同。(这是继承与多态思想的典型应用:表现不同的行为)
8. Queue
队列是一个典型的先进先出(FIFO)的 容器。即从容器的一端放入事物,从另一端取出,并且事物放入容器的顺序与取出的顺序是相同的。队列常被当做一种可靠的将对象从程序的某个区域传输到另一个区域的途径。
9. 总结
Java提供了大量持有对象的方式:
1)数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的、可以保存基本类型的数据。但是,数组一且生成,其容量就不能改变。
2) Collection保存单一的元素,而Map保存相关联的键值对。有了Java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在从容器中获取元素时,不必进行类型转换。各种 Collection和各种Map都可以在你向其中添加更多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中
所持有的包装器类型之间的双向转换。
3)像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能够自动扩充容量。
4)如果要进行大量的随机访问,就使用 ArrayList;如果要经常从表中间插入或删除元素,则该使用 LinkedList。
5)各种 Queue以及栈的行为,由 LinkedList提供支持。
6)Map是一种将对象(而非数字)与对象相关联的设计。 HashMap设计用来快速访问。而Treemapf保持“键”始终处于排序状态,所以没有 Hashmapt快。 LinkedMap保持元素插人的顺序,但是也通过散列提供了快速访问能力。
7)Set不接受重复元素。 HashSet提供最快的查询速度,而 TreeSet保持元素处于排序状态,LinkedHashSet以插入顺序保存元素。
8)新程序中不应该使用过时的 Vector、 Hashtable和 Stack。
浏览一下Java容器的简图(不包含抽象类和遗留构件)会大有裨益。这里只包含你在一般情况下会碰到的接口和类。
你可以看到,其实只有四种容器:Map、List、Set和 Queue,它们各有两到三个实现版本( Queue的java.util.concurrent实现没有包括在上面这张图中)。常用的容器用黑色粗线框表示。
点线框表示接口,实线框表示普通的(具体的)类。带有空心箭头的点线表示一个特定的类实现了一个接口,实心箭头表示某个类可以生成箭头所指向类的对象。例如,任意的Collection可以生成 Iterator,而List可以生成 ListIterator(也能生成普通的 Iterator,因为List继承自 Collection)。