一般,咱们老是在程序运行过程当中才得到一些条件去建立对象,这些动态建立的对象就须要使用一些方式去保存。咱们可使用数组去存储,可是须要注意数组的尺寸一旦定义便不可修改,而咱们并不知道程序在运行过程当中会产生多少对象,因而数组的尺寸便成了限制。Java实用类库还提供了一套的容器类来解决这个问题,基本类型为:List 、Set、Queue和Map。这些对象类型也称为集合类,可是因为Java类库使用了Collection这个名字来指代该类库中的一个特殊子集,因此使用术语“容器”来称呼它们。下面将简单介绍Java容器类库的基本概念和功能、每一个容器的不一样版本实现和和从总体分析容器之间的联系。html
Java容器类库的主要做用是“保存对象”,咱们将其划分红如下两个不一样的概念:java
Collection算法
一个独立的元素序列(一种存放一组对象的方式),这些元素都服从一条或多条规则。List必须按照插入的顺序保存元素;Set中不能有重复的元素;Queue按排队规则来肯定对象产生的顺序。编程
Collection是一个高度抽象的容器接口,其中包含了容器的基本操做和属性。数组
Map安全
一组成对的“键值对”对象,容许你使用键来查找值。数据结构
框架类图中还包含了许多Abstract类,主要方便于咱们建立容器的实例,Abstract类中已基本实现了接口中的方法,咱们只须要选择咱们须要的方法进行覆盖便可。并发
Iteratorapp
咱们再来看Iterator,咱们一般是使用Iterator迭代器来遍历容器。上图存在的Collection依赖于Iterator是指:实现Collection须要实现iterator()函数,能够返回一个Iterator对象。ListIterator是专门用于遍历List的迭代器。框架
工具类Arrays和Collections为容器添加元素
java.util包中的Arrays和Collections类中包含了不少的实用方法。Arrays类中包含操做数组的各类方法,还包含一个静态的Arrays.asList()方法接受一个数组或是用逗号分隔的元素列表,将其转换成一个列表对象。Collection类包含对集合操做的各类方法。咱们也可使用Collections.addAll()想容器中添加一组元素。Collections.addAll()接受一个Collection对象以及一个数组或者用逗号分隔的元素列表,将元素添加到Collection对象中。
Arrays.asList()的底层实现是一个数组,即便用Arrays.asList()生成的List的尺寸是不能够修改的(添加或删除元素),不然将会抛出UnsupportedOperationException异常。
List接口继承自Collection接口,用于Collection中的全部方法,在Collection的基础上也添加了许多方法,使得能够在List中插入和删除元素。List有两种基本的实现:ArrayList和LinkedList
Set中不保存重复的元素,含义同数学概念上的集合。 Set经常使用于测试归属性,即查询某个元素是否在某个Set中。正由于如此查找也就成了Set中重要的操做。一般会选择HashSet的实现,它对快速查找进行了优化。Set也有多种不一样的实现,不一样的Set实现不只具备不一样的行为,并且它们对于能够在特定的Set中防止元素的类型也有不一样的要求。
Set(interface)
存入Set的每一个元素必须是惟一对的。加入Set的元素必须定义equals()方法以确保对象的惟一性。Set接口不保证维护元素的次序。
HashSet
为快速查找而设计的Set。存入HashSet的元素必须定义hashCode()。使用HashMap实现。
TreeSet
保持次序的Set,底层为树结构。使用它能够从Set中提取有序的序列。元素必须实现Comparable接口。使用TreeSet实现。
LinkedHashSet
具备HashSet的查询速度,且内部使用链表维护元素的顺序(插入的次序)。在使用迭代器遍历该Set时,结果会按照元素插入的次序显示。元素也必须定义hashCode()方法
Map有如下特色:
Map是将键映射到值的键值对(key-value)接口
映射中不能包含重复的键,每一个键最多能够映射到一个值,可是一个值能够被多个键映射
Map提供了三个Set视图供咱们访问:键的Set、值的Set和键值对的Set
映射的顺序定义为访问的映射Set上的迭代器返回元素的顺序。TreeMa类,能够对映射的顺序作出特定保证;其余的,则不能保证
可变对象做为映射键须要很是当心
Map的实现类应该提供两个“标准“构造函数
第一个,void(无参数)构造方法,用于建立空映射
第二个,带有单个 Map 类型参数的构造方法,用于建立一个与其参数具备相同键-值映射关系的新映射。带有单个 Map 类型参数的构造方法,用于建立一个与其参数具备相同键-值映射关系的新映射
Map的几种基本实现:
HashMap
Map是基于散列表的实现(取代了HashTable)。HashMap使用散列码(对象hashCode()生成的值)来进行快速搜索。
LinkedHashMap
相似于HashMap,可是迭代的时候,取得键值对的顺序是起插入的顺序,或者是最近最少使用(LRU)的次序。只比HashMap慢一点,而迭代访问的时候更快,由于使用链表维护了内部次序。
TreeMap
基于红黑树的实现。查看“键”或者“键值对”时,它们会被排序(次序由Comparable或Comparator决定)。TreeMap的特色在于所获得的结果是通过排序的。TreeMap是惟一的带有subMap()的Map,能够返回一个子树。
WeakHashMap
弱键(weak key)映射,容许释放映射所指向的对象,这是为了解决某类特殊问题而设计的。若是映射以外没有引用指向某个“键”,则此“键”能够被垃圾回收。
ConcurrentHashMap
一种线程安全的Map,不涉及同步加锁。在并发中还会介绍。
Stack是一个先进后出(LIFO)的容器。往盒子中放书,先放进去的最后才拿得出来,最后放进去的第一个就能够取出,这种模型就是栈(Stack)能够描述的。LinkedList中有能够实现栈全部功能的方法,有时也能够直接将LinkedList做为栈使用。
队列是一个典型的先进先出(FIFO)的容器。事物放进容器的顺序和取出的顺序是相同的(优先级队列根据事物优先级出队事物)。队列常被当作一种可靠的将对象从程序的某个区域传输到另外一个区域的途径。队列在并发编程中特别重要。一样,LinkedList也提供了方法支持队列的行为,而且它实现了Queue接口。
先进先出描述了典型的队列规则。队列规则是指在给定一组队列的元素状况下,肯定下一个弹出队列的元素的规则。优先级队列声明的下一个弹出的元素是最须要的元素(具备最高优先级的元素)。
咱们能够在PriorityQueue上调用offer()方法来插入一个对象,这个对象就会在队列中被排序,默认排序为天然排序,即按插入的前后进行排序,可是咱们能够经过提供本身的Comparator来修改这个排序。当调用peek()、poll()和remove()方法时,将获取队列优先级最高的元素。
优先级队列算法实现的数据结构一般是一个堆。
对于访问容器而言,有没有一种方式使得同一份遍历的代码能够适用于不一样类型的容器?要实现这样的目的就可使用迭代器。使用迭代器对象,遍历并选择序列中的对象,而客户端没必要知道或关心该序列底层的结构。Java中对迭代器有一些限制,好比Java的Iterator只能单向移动,这个Iterator只能用来:
API中的Iterator接口中方法如上,实现Iterator对象须要实现hashNext()方法和next()方法,remove方法是一个可选操做。forEachRemaining是Java 1.8(Java SE8)中加入的方法,用于Lambda表达式。
举一个简单的使用迭代器访问容器的例子:
class Cat{ private static int counter = 0; private int id = counter++; @Override public String toString() { return "Cat: " + id; } } public class IteratorAccessContainer { //不包含任何容器类型信息的遍历容器方法 public static void showElement(Iterator<Cat> it) { while (it.hasNext()) { //hasNext()检查序列中是否还有元素 Cat cat = it.next(); //next()返回序列中的元素 System.out.print(cat + "\t"); } System.out.println(); } public static void main(String[] args) { ArrayList<Cat> cats1 = new ArrayList<Cat>(); LinkedList<Cat> cats2 = new LinkedList<>(); //能够省略类型参数 编译器可自动推断出 HashSet<Cat> cats3 = new HashSet<>(); for(int i=0;i<3; i++) { cats1.add(new Cat()); cats2.add(new Cat()); cats3.add(new Cat()); } showElement(cats1.iterator()); showElement(cats2.iterator()); showElement(cats3.iterator()); } } /* output: Cat: 0 Cat: 3 Cat: 6 Cat: 1 Cat: 4 Cat: 7 Cat: 2 Cat: 8 Cat: 5 */
showElement()方法不包含任何有关它遍历的序列类型信息,这就展现了Iterator的好处:可以将遍历序列的操做与序列底层结构分离。也能够说,迭代器统一了对容器的访问方式。
从容器框架图中咱们能够看出,Collection是描述全部序列容器的共性的根接口。可是在C++中,标准的C++类库中没有其余容器的任何公共基类,容器之间的共性都是经过迭代器达成的。在Java中,则将两种方法绑定到了一块儿,实现Collection的同时也要实现iterator()方法(返回该容器的迭代器)。
ListIterator是一个更增强大的Iterator子类型,可是它只能用于各类List的访问。Iterator只能前向移动,但ListIterator容许咱们能够先后移动。它还能够产生相对于迭代器在列表中指向当前位置的前一个和后一个索引,而且可使用set()方法替换它访问过的最后一个元素。remove()方法能够删除它访问过的最后一个元素。须要注意,这两处的最后一个元素只的都是调用next()或者previous返回的元素,也就意味着调用set()、remove()这两个方法以前,要先调用next()或者previous()。
须要注意ListIterator在序列中的游标位置与Iterator不一样,Iterator的游标位置始终位于调用previous()将返回的元素和调用next()将返回的元素之间。长度为n的列表的迭代器的游标位置有n+1个。
使用ListIterator对列表进行正向和返回迭代,以及使用set()替换列表元素的例子:
public class ListIteration { public static void main(String[] args) { List<Cat> catList = new ArrayList<>(); for(int i=0; i<5; i++) { catList.add(new Cat()); } ListIterator<Cat> it = catList.listIterator(); System.out.println("CatNo.\t nextIndex\t previousIndex"); //正向遍历 System.out.println("正向遍历:"); while (it.hasNext()) { Cat cat = it.next(); System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex()); } System.out.println(); System.out.println("当迭代器游标处于最后一个元素末尾时:"); ListIterator<Cat> it2 = catList.listIterator(); while (it2.hasNext()) { Cat cat = it2.next(); System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex()); } System.out.println(); //反向遍历 System.out.println("反向遍历"); while(it.hasPrevious()) { Cat cat = it.previous(); System.out.println(cat+"\t\t"+it.nextIndex()+"\t\t"+it.previousIndex()); } System.out.println(); //产生指定游标位置的迭代器 从第二个位置开始向前替换列表中的Cat对象 System.out.println("从第二个位置开始向前替换列表中的Cat对象"); it = catList.listIterator(2); while(it.hasNext()) { it.next(); it.set(new Cat()); } System.out.println(catList); } } /* CatNo. nextIndex previousIndex 正向遍历: Cat: 0 1 0 Cat: 1 2 1 Cat: 2 3 2 Cat: 3 4 3 Cat: 4 5 4 当迭代器游标处于最后一个元素末尾时: Cat: 0 5 4 Cat: 1 5 4 Cat: 2 5 4 Cat: 3 5 4 Cat: 4 5 4 反向遍历 Cat: 4 4 3 Cat: 3 3 2 Cat: 2 2 1 Cat: 1 1 0 Cat: 0 0 -1 从第二个位置开始向前替换列表中的Cat对象 [Cat: 0, Cat: 1, Cat: 5, Cat: 6, Cat: 7] */
foreach语法不只能够用在数组,也能够用在任何Collection对象。之因此能够用在Collection对象,是由于Java SE5引入了Iterable
接口,该接口包含一个可以产生Iterator的iterator()方法,而且Iterable接口被foreach用来在序列中移动。所以,若是建立了任何实现Iterable的类,均可以将它用于foreach当中。须要注意,数组虽然可使用foreach语法遍历,但不意味着数组是Iterable的。
实现一个可迭代的类,使用foreach方法遍历
public class IterableClass implements Iterable<String>{ private String[] words = ("This is happy day.").split(" "); @Override public Iterator<String> iterator() { return new Iterator<String>() { private int index = 0; //判断是否存在下一个元素 public boolean hasNext() { return index < words.length; } //返回下一个元素 public String next() { return words[index++]; } public void remove() { //remove能够不用实现 throw new UnsupportedOperationException(); } }; } public static void main(String[] args) { //foreach语法遍历实现了Iterable接口的类 for(String s : new IterableClass()) { System.out.println(s); } } } /* This is happy day. */
对Java容器类库作了大体的介绍,具体的容器使用方法以及实现会在后面的博客中继续介绍。本文重点介绍了Iterator,它统一了对容器的访问方式,可是仍有一点心存疑惑:foreach语法能够遍历容器是由于容器实现了Iterable的缘由,可是也能够遍历数组,数组并非Iterable的。那么foreach能够遍历数组的依据是什么呢?这个问题暂时尚未看到合适的解答,各位看官如有想法可留言告知,感激涕零!
参考:
Java 集合系列目录(Catedory): http://www.javashuo.com/article/p-tjlggnrm-hq.html
《Java编程思想》第四版