容器,和我们平常说的集合是指的一个概念。
为什么需要容器
如果一个程序只包含固定数量的且其生命期都是已知的对象,那么这是一个非常简单的程序。
但是通常,程序问题根据运行时才知道的某些条件支创建新对象。在此之前,不会知道所需对象的数量,甚至不知道确切的类型。这是一个编程语言设计角度的问题,为了解决这个普遍的编程问题,需要在任意时刻任意位置创建任意数量的对象。不能依靠创建命名的引用来持有每一个对象,如:
MyType aReference;
从这个角度来讲,这个问题不仅是java需要关注的问题也是所有编程语言需要关注的问题,而大多数语言都提供了某种方法来解决这个基本问题。
java有多种方式保存对象(更准确的说是保存对象的引用)。比如数组。但数组具有固定的尺寸。但是我们写程序是往往是无法预知有多少个对象的。java容器类库提供了一套相当完整的容器类来解决这个问题,其中最基本的类型是List,Set,Queue和Map。它们各自有自己的特点。
我们使用了Collection容器这个范围更大的术语称呼它们。
基本概念
java集合类库的用途是保存对象。java体系里的集合从继承关系来看可分为两个部分,一个是Map接口下的,另一个是Collection下的。
Map
一组成都的键值对对象,允许使用键来查找值。映射表允许我们使用一个对象来查找另一个对象,它也被称为关联数组,因为它将某些对象与另外一些对象关联在了一起,或者被称为字典,因为你可以用键对象来查找值对象,就像在字典中查单词一样。Map是强大的编程工具。
两个接口下的实现类,接口非常之多。在JDK1.8中,Map的一级子类大概如下:
注意,这里仅仅是一级子类,他们下面还有许多实现类,想全部弄懂是一件不可能的事情也是没有必要的事情。下面我将map下面较为常见的类和接口做了一个总结,他们的关系如下:
灰色:接口interface
橙色:类class
我们常见的HashMap,ConcurrentHashMap,HashTable和TreeMa都已经包括在了其中。他们分别的实现可以参考我的另一篇博文:链接。
Collection
一个独立元素的序列序列,这些元素都服从一条或多条规则。比如List必须按照插入的顺序保存元素,而Set不能有重复元素,Queue按照排队规则来确定对象的顺序。
Collection的一级子类在这里不再列出来。同样我们将Collection下面常用的类做一个总结,其结构如下:
很有意思的现象是和map相比,
- Collection在最上层就继承了Iterable接口,这意味着从设计的角度来讲,整个Collection架构都是设计为可被用于遍历循环的。
- Collection在定义的时候就有一个参数<E>,而Map在定义的时候参数即为<K,V>,这其实意为着在设计之处,整个Map架构就是用于键值对,key-value这样的模式,而Collection这是一个纯粹的容器,可以放任何的对象并且对他们进行批量操作
- 这个图不完全准确,但是大体的架构和继承关系是正确的。仅供参考。
Collection的功能方法
下面的表格列出了可以通过Collection执行的所有操作(不包括从Object继承而来的方法)。因此,它们也是可通过Set或List执行的所有操作(List还有额外的功能)
请注意,其中不包括随机访问所选择元素的get()方法。因为Collection包括Set,而Set是自己维护内部顺序的(这使得随机访问变得没有意义)。因此,如果想检查Collection中的元素,那就必须使用迭代器。
各种容器的特点
1)数组将数字与对象联系起来。它保存类型明确的对象,查询对象时,不需要对结果做类型转换。它可以是多维的,可以保存基本数据类型的数据。但是,数组一旦生成,其容量就不能改变。
2)Collection保存单一的元素,而Map保存相关联的键值对。有了Java的泛型,你就可以指定容器中存放的对象类型,因此你就不会将错误类型的对象放置到容器中,并且在从容其中获取元素时,不必进行类型转换。各种Collection和各种Map都可以在你向其中添加跟多的元素时,自动调整其尺寸。容器不能持有基本类型,但是自动包装机制会仔细地执行基本类型到容器中所有的包租昂其类型之间的双向转换。
3)像数组一样,List也建立数字索引与对象的关联,因此,数组和List都是排好序的容器。List能自动扩充容量。
4)如果要进行大量的随机访问,就用ArrayList;如果经常从表中间插入或删除元素,则应该使用LinkedList。
5)各种Queue以及栈的行为,有LinkedList提供支持。
6)Map是一种将对象(而非数组)与对象相关联的设计。HashMap设计用来快速访问;而TreeMap保持“键”始终处于排序状态,所以没有HashMap快。LinkedListHashMap保持元素插入的顺序,但是也通过散列提供了快速访问的能力。
7)Set不接受重复元素。HashSet提供最快的查询速度,而TreeSet保持元素处于排序状态。LinkedHashSet以插入顺序保持元素。
8)新程序中不应该使用过时的Vector、Hashtable和Stack。
添加一组元素
Arrays.asList方法接受一个数组或是一个用逗号分割的元素列表,它使用可变参数,并将其转换为一个List对象。
Collections.addAll方法接受一个Collection对象,以及一个数组或是一个由逗号分割的列表,将元素添加到Collection中。
下面的代码是一个使用演示:
public class AddingGroups {
public static void main(String[] args) {
Collection<Integer> collection = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4, 5));
Integer[] moreInts = {6, 7, 8, 9, 10};
collection.addAll(Arrays.asList(moreInts));
Collections.addAll(collection, 11, 12, 13, 14, 15);
Collections.addAll(collection, moreInts);
List<Integer> list = Arrays.asList(16, 17, 18, 19, 20);
list.set(1, 99);//modify an element
}
}
List接口在Collection的基础上添加了大量的方法,使得可以在List中间插入和删除元素。
有两种类型的List:
ArrayList,它长于随机访问元素,但是在List的中间插入和移除元素的时候较慢。
LinkedList,它在List中间进行插入和删除的操作代价较低,但是在随机访问方面相对较慢,但是它的特性集比ArrayList更大。
关于这两个类的详细信息,请参考我的文章: [java] ArrayList和LinkedList实现原理及比较.
迭代器
迭代器,也是一种设计模式。迭代器是一个对象,它的工作遍历并选择序列中的对象,而程序员不用关心该序列底层的结构。迭代器通常被称为轻量级对象,创建它的代价小。java的Iterator只能单向移动,这个Iterator只能用来:
- 使用方法iterator()要求容器返回一个Iterator。Iterator将准备好返回序列中的第一个元素。
- 使用next()获得序列中的下一个元素
- 使用hasNext()检查序列中是否还要元素
- 使用remove()将迭代器新返回的元素删除
这四个功能中的后三个方法和Iterator这个接口定义的几个方法都是一一对应:
/**
* Returns {@code true} if the iteration has more elements.
* (In other words, returns {@code true} if {@link #next} would
* return an element rather than throwing an exception.)
*
* @return {@code true} if the iteration has more elements
*/
boolean hasNext();
/**
* Returns the next element in the iteration.
*
* @return the next element in the iteration
* @throws NoSuchElementException if the iteration has no more elements
*/
E next();
/**
* Removes from the underlying collection the last element returned
* by this iterator (optional operation). This method can be called
* only once per call to {@link #next}. The behavior of an iterator
* is unspecified if the underlying collection is modified while the
* iteration is in progress in any way other than by calling this
* method.
*
* @implSpec
* The default implementation throws an instance of
* {@link UnsupportedOperationException} and performs no other action.
*
* @throws UnsupportedOperationException if the {@code remove}
* operation is not supported by this iterator
*
* @throws IllegalStateException if the {@code next} method has not
* yet been called, or the {@code remove} method has already
* been called after the last call to the {@code next}
* method
*/
default void remove() {
throw new UnsupportedOperationException("remove");
}
但是第一个功能:使用方法iterator()要求容器返回一个Iterator。在Iterator接口里面却找不到定义。我们需要从Iterator这个接口和容器的关系着手研究这个问题。
Collection和Iterator是组合的关系。
这意味着在Collection容器中会持有一个Iterator的引用,事实上也确实如此,Collection中的代码如下:
/**
* Returns an iterator over the elements in this collection. There are no
* guarantees concerning the order in which the elements are returned
* (unless this collection is an instance of some class that provides a
* guarantee).
*
* @return an <tt>Iterator</tt> over the elements in this collection
*/
Iterator<E> iterator();