一般来说,你并不知道解决一个特定的问题需要用到多少个对象,也不知道这些对象会存在多久,你甚至不知道该如何保存这些对象。问题是,如果你无法在程序运行前确切地知道这些信息,那你应该申请多少内存空间呢?
在面向对象设计领域,大多数问题的解决方案看似极为简单粗暴:创建一种新类型的对象,这种对象通过保存其他对象的引用来解决这个问题。而在大多数编程语言里,你也可以用 数组 (array)做到这一点。
这种新对象通常叫作 集合 (也可以叫作“容器”,不过Java的库普遍使用的是“集合”),它会根据你放入其中的内容自行调整空间。也就是说,你无须关注集合里会有多少对象,直接创建集合就好了,剩下的细节交给它自己处理就可以。
幸运的是,优秀的面向对象语言都会提供一些集合作为语言的基础功能。在C++里,集合是C++标准库的一部分,通常叫作“标准模板库”(Standard Template Library,STL)。SmallTalk提供了一系列完整的集合。Java在其标准库中也提供了大量的集合。在有些语言的库中,通常会有一两个集合能够适用于所有需求。而在另外一些语言(比如Java)的库中,不同的集合具有不同的用途。比如,有几个不同的List类(用于保存序列),几个Map类(也叫“关联数组”,用于关联对象),几个Set类(用于保存不同类型的对象),以及一些队列(queue)、树(tree)、栈(stack)等。
从程序设计的角度而言,你真正需要的是能够解决实际问题的集合。一旦某种集合能够满足你的需求,你就不再需要其他集合了。之所以需要选择集合,可能有以下两个原因。
不同的集合提供了不同类型的接口和行为。比如,栈和队列的用途就与Set以及List完全不同。针对你的问题,其中的某个集合也许可以提供比另一个集合更灵活的解决方案。
不同的集合在特定操作的执行效率方面也会有差异。比如,List有两种基础类型的集合:ArrayList和LinkedList。虽然两者可以具有相同的接口和行为,但是某些操作的执行效率却存在明显的差异。比如用ArrayList随机获取元素是一种耗费固定时间的操作,意思是不管你选择获取哪个元素,耗费的时间都是相同的。但是对于LinkedList来说,在列表中随机选择元素是一种代价很大的操作,查找列表更深处的元素也会耗费更多的时间。另外,如果需要在列表中插入元素,LinkedList耗费的时间会比ArrayList更少。取决于两者底层架构的不同实现方式,其他一些操作的执行效率也各有不同。你也可以先用LinkedList编写代码,然后为了追求效率而转投ArrayList的怀抱。由于两者都是基于List接口的子类,因此只需要改动少量代码就可以切换集合。