java.util之集合框架

JDK8在线Api中文手册java

JDK8在线Api英文手册
web

1 集合概述

   Java集合框架标准化了程序处理对象组的方式。集合不是原始Java发布版本的一部分,可是在J2SE1.2版本就已经添加。在集合框架以前,Java提供了特定的类以存储和管理对象组。例如Dictionary、Vector、Stack和Properties。尽管这些类至关有用,可是它们缺乏集中、同一的主题。例如,Vector的使用方式与Properties不一样。此外,早期的专业方式没有被设计成以扩展、易改造的形式。集合是解决这些(以及其余)问题的答案。
   集合框架设计上须要知足几个目标。首先,框架必须是高性能的。基本集合(动态数组、链表、树以及哈希表)的实现是高效率的。不多须要手动编写这些“数据引擎”中的某个。其次,框架必须容许不一样类型的集合以相似的方式进行工做,而且具备高度的互操做性。再次,扩展和/或改造集合必须易于实现。为了知足这些目标,整个集合框架基于一套标准接口进行构造,提供了这些接口的一些能够直接使用的标准实现(例如LinkedList、HashSet和TreeSet)。做为一种选择,也能够实现本身的集合。为了方便,提供各类特定目的的实现,而且提供一些部分实现,从而使咱们能够更容易地建立本身的集合类。最后,必须添加能够将标准数组集成到集合框架中的机制
   算法是集合机制的另一个重要部分。算法操做集合,而且被定义为Collections类中的静态方法。所以全部集合均可以使用它们。每一个集合类都不须要实现特定于本身的版本,算法为操做集合提供了标准的方式。
   与集合框架密切相关的另外一个内容是Iterator接口。迭代器为访问集合中的元素提供了通用、标准的方式,每次访问一个元素。所以,迭代器提供了美剧集合内容的一种方式。由于每一个集合都提供了迭代器,因此能够经过Iterator定义的方法访问全部集合类的元素。所以,对循环遍历集合的代码进行很小的修改,就能够将其用于循环遍历列表。
   JDK8添加了另外一种类型的迭代器,叫作spliterator。简单来讲,spliterator就是并行迭代支持的迭代器。支持spliterator的接口有Spliterator和支持基本类型的几个嵌套接口。JDK8还添加了用于基本类型的迭代器接口,例如PrimitiveIterator和PrimitiveIterator.OfDouble。
   除了集合外,框架还定义了一些映射接口和类。映射存储键/值对。尽管映射是集合框架的组成部分,可是从严格意义上讲,它们不是集合。不过,能够得到映射的集合视图(collection-view)。这种视图包含来自映射的元素,并存储在集合中。所以,做为一种选择,能够像处理集合那样处理映射的内容。
   集合机制改造了java.util定义的某些原始类,从而使它们也能够被集成到新系统中。尽管几个替代了许多原始使用工具类的架构,可是不会形成任何类过期,集合只是为作某些事情提供了更好的方式。算法

2 JDK5对集合框架的修改

   在发布JDK5时,对集合框架进行了一些根本性的改变,这些修改显著加强了集合框架的功能并简化了它的使用。这些修改包括增长泛型、自动装箱/拆箱以及for-each风格的for循环。尽管JDK8是JDK5以后的第三个主要Java发布版本,可是JDK5特性的效果是如此深远,以致于它们仍然使人特别关注。主要缘由是大量JDK5之前的代码仍然在使用。数据库

2.1 泛型从根本上改变了集合框架

   新增的泛型造就了集合框架的一个重要改变,由于整个集合框架针对泛型进行了从新设计。全部集合如今都是泛型的。而且许多操做集合的方法如今采用泛型参数,简单地讲,新增的泛型影响了集合框架的每一部分。
   泛型添加了集合一直确实的一个特性:类型安全性。在泛型之前,全部集合都存储Object引用,这意味着全部集合能够存储任何类型的对象。所以,可能会无心地在集合中存储不兼容的类型。若是出现这种状况,就会致使运行时类型不匹配错误。使用泛型,能够明确地代表将要存储的数据类型,从而能够避免运行时类型不匹配错误。
   尽管新增的泛型彻头彻尾地改变了大部分集合和接口声明,以及它们的一些方法。但整体而言,集合框架的工做方法会与泛型出现之前是相同的。固然,为了获取泛型为集合带来的好处,须要从新编写旧代码。这也很重要,由于使用现代Java编译器编译在泛型出现以前的代码,会产生警告信息。为了消除这些警告,须要为全部集合代码添加类型信息。express

2.2 自动装箱使得使用基本类型更加容易

   自动装箱/拆箱方便了在集合中存储基本类型。集合只能存储引用,不能存储基本类型值。在过去,若是但愿在集合中存储基本类型值,如int,就必须手动将数值装箱到相应的类型封装器中。当检索数值时,须要(使用显式的类型转换)手动拆箱为正确的基本类型。由于提供了自动装箱/拆箱特性。当存储或检索基本类型时,Java能够自动执行须要的装箱和拆箱操做。再也不须要手动执行这些操做。编程

2.3 for-each风格的for循环

   Java对集合框架中的全部集合进行了从新设计以实现Iterable接口。这觉得着可使用foreach风格的for循环遍历集合。在过去,遍历集合须要手动构造循环来使用迭代器。尽管在许多状况下,为了某些用途仍然须要迭代器,可是可使用for循环替代基于迭代器的循环。api

3 集合接口

   集合框架定义了一下核心接口,下面是对每一个接口进行简要介绍。首先介绍集合接口是又必要的,由于它们决定了集合的本质特性。换句话说,具体类只是提供了标准接口的不一样实现。表1汇总了奠基集合基础的那些接口。数组

表 1 奠基集合基础的接口
接 口 描 述
Collection 容许操做一组对象,位于集合层次结构的顶部
Deque 扩展Queue以处理双端队列
List 扩展Collection以处理序列(对象列表)
NavigableSet 扩展SortedSet以基于最接近匹配原则检索元素
Queue 扩展Collection以处理特殊类型的列表,这种类型的列表只能从列表顶部删除元素
Set 扩展Collection以处理集合,集合中的元素必须惟一
SortedSet 扩展Set以处理已排序的集合

   除了集合接口外,集合还使用Comparator、RandomAccess、Iterator以及ListIterator接口。从JDK8开始,还使用Spliterator接口。简单地讲,Comparator定义了如何比较两个对象:Iterator、ListIterator和Spliterator枚举集合中的对象。经过实现RandomAccess,能够代表列表支持高效、随机的元素访问。
   为了提供最大的灵活性,集合接口容许某些方法是可选的,可选方法容许修改集合的内容。支持这些方法的集合被称为是可修改的,不容许修改内容的集合被称为不可修改集合,若是试图在不可修改的集合上使用这些方法,就会抛出UnsupportedOperationException异常。全部内置集合都是可修改的。
   接下来分析各类集合接口。安全

3.1 Collection接口

   Collection接口是构建集合框架的基础,由于定义集合的全部类都必须实现该接口。Collection接口是泛型接口,其声明以下:数据结构

interface Collection<E>

   其中,E指定了集合将要存储的对象的类型。Collection扩展了Iterable接口,这意味着全部集合均可以使用for-each风格的for循环进行遍历(只有实现了Iterable接口的类才可以经过for循环进行遍历)。
   Collection声明了全部集合都将拥有的核心方法。表2对这些方法进行了总结。由于全部集合都实现了Collection接口,因此为了清晰理解框架,熟悉接口方法时有必要的。这些方法中的某些方法可能抛出UnsupportedOperationException异常。若是集合不能修改就可能发生这种状况。若是一个对象和另外一个对象不兼容,那么会生成ClassCastException异常,例如当试图将一个不兼容的对象添加到集合中时,就会发生这种状况。若是试图在不容许存储null对象的集合中存储null对象,那么就会跑出NullPointerException异常,若是使用的参数无效,那么会抛出IllegalArgumentException异常。若是试图为长度固定而且已经满了的集合添加元素,那么会抛出IllegalStateException异常。

表2 Collection接口声明的方法
方 法 描 述
boolean add(E obj) 将obj添加到调用集合中,若是obj被添加到集合中,就返回true;若是obj已是集合的成员而且集合的元素不容许重复,就返回false
boolean addAll(Collection<? extends E> c) 将c中的全部元素添加到调用集合中,若是调用几个发生了变化(例如添加了元素),就返回true,不然就返回false
boolean contains(Object obj) 若是obj是调用集合中的元素,就返回true;不然返回false
boolean containsAll(Collection<?> c) 若是调用集合包含c的全部元素,就返回true;不然返回false
boolean equals(Object obj) 若是调用集合与obj相等,就返回true;不然返回false
int hashCode() 返回调用集合的散列码
boolean isEmpty() 若是调用集合为空,就返回true;不然返回false
Iterator<E> iterator 返回调用集合的一个迭代器
default Stream<E> parallelStream 返回一个使用调用集合做为元素来源的流,该流可以支持并行操做(JDK8新增)
boolean remove(Object obj) 从调用集合中移除obj的一个实例,若是移除了元素,就返回true;不然返回false
boolean removeAll(Collection<?> c) 从调用集合中移除c的全部元素。若是操做集合发生了变化(移除了元素),就返回true;不然返回false
default boolean removeIf(Predicate<? super E> predicate) 从调用集合中移除知足predicate指定条件的那些元素(JDK8新增)
boolean retainAll(Collection<?> c) 移除调用集合中除c中元素以外的全部元素。若是集合发生了变化(即移除了元素),就返回true;不然返回false
int size() 返回调用集合元素的数量
default Spliterator<E> spliterator() 返回调用集合的Spliterator(JDK8新增)
default Stream<E> stream() 返回一个使用调用集合做为元素来源的流。该流是顺序流(JDK8 新增)
Object[] toArray() 返回包含调用集合中存储的全部元素的数组,数组元素是结合元素的副本
<T> T[] toArray(T array[]) 返回包含调用集合中元素的数组。数组元素是集合元素的副本。若是array的长度等于元素的数量,就将返回的元素保存啊在array中;若是array的长度小于元素的数量,就分配必需大小的新数组并返回这个新数组;若是array长度大于元素的数量,就将最后一个集合元素以后的数组元素设置为null;若是全部集合元素的类型都不是array的子类型,那么就抛出ArrayStoreException异常

   经过调用add()方法能够将对象添加到集合中。注意,add()方法采用类型为E的参数,这觉得着添加到集合中的对象必须和集合所指望的对象相兼容。经过调用addAll()方法,能够将一个集合的全部内容添加到两一个集合中。
   经过使用remove()方法能够移除一个对象。为了移除一组对对象,须要调用removeAll()方法。经过调用retainAll()方法,能够移除除了制定元素以外的全部元素。从JDK8开始,要想仅移除知足某些条件的元素,可使用removeIf()方法(Precidate是JDK8新增的一个函数式接口)。为了清空集合,能够调用clear()方法。
   经过调用contains()方法,能够肯定集合是否包含特定的对象。为了肯定一个集合是否包含另外一个集合的全部成员,能够调用containsAll()方法。经过调用isEmpty()方法能够肯定集合是否为空。经过调用size()方法能够肯定集合当前包含的元素的数量。
   toArray()方法返回一个数组,其中包含调用集合中存储的元素。改方法的第一种形式返回Object类型的数组;第二种形式返回的数组,与指定为参数的数组具备相同的类型。一般,第二种形式更方便,由于可以返回指望的数组类型。这些方法比它们初看起来更重要。一般,使用相似数组的语法处理集合的内容是有利的。经过提供集合和数组之间的转换途径,能够更好地利用二者的优势。
   经过调用equals()方法能够比较;两个集合的相等性。"相等"的精确含义依据集合的不一样能够有所区别。例如,能够实现equals()方法,从而比较存储在集合中的元素的值。或者,equals()方法也能够比较对这些元素的引用。
   另外一个重要的方法时iterator(),它返回集合的一个迭代器。新的Spliterator()方法返回集合的一个spliterator。当操做集合时会频繁用到迭代器。最后,stream()和parallelStream()方法返回使用集合做为元素来源的流。

3.2 List接口

   List接口扩展了Collection,而且声明了用来存储一连串元素的集合的行为。在列表中,可使用从0开始的索引,经过元素的位置插入或访问元素。列表能够包含重复的元素。List是泛型接口,其声明以下:

interface List<E>

   其中,E指定了将存储于列表中的对象的类型。
   除了Collection定义的方法外,List还定义了本身的一些方法,表3汇总了这些方法。请注意,若是列表是不能修改的,那么这些方法中的某些方法会抛出UnsupportedOpreationException异常;而且若是一个对象和另外一个对象不兼容,那么抛出ClassCastException异常,例如当试图将不兼容的对象添加到列表中时,就会排除该异常。此外,若是使用的索引无效,一些方法会抛出IndexOutOfBoundsException异常。若是试图在不容许存储null对象的列表中存储null对象,那么会抛出NullPointerException异常。若是使用的参数无效,那么会抛出IllegalArgumentException异常。

表3 List接口声明的方法
方 法 描 述
void add(int index,E obj) 将obj插入到调用列表中由index指定的索引位置。在插入点及以后位置存储的元素被后移,所以没有元素会被覆盖
boolean addAll(int index,Collection<? extends E> c) 将c中的全部元素插入到列表中由index指定的索引位置。任何在插入点以及以后位置存储的元素都将后移,所以没有元素会被覆盖。若是过调用列表发生了变化,就返回true;不然返回false
E get(int index) 返回调用集合中在指定索引位置存储的对象
int indexOf(Object obj) 返回调用列表中第一个obj实例的索引。若是obj不是列表中的元素,就返回-1
int lastIndexOf(Object obj) 返回调用列表中最后一个obj实例的索引。若是obj不是列表中的元素,就返回-1
ListIterator<E> listIterator 返回调用列表的一个迭代器,该迭代器从列表的开头开始
ListIterator<E> listIterator(int index) 返回调用列表的一个迭代器,改迭代器从列表中index指定的索引位置开始
E remove(int index) 从调用列表中移除位于index索引位置的元素,并返回被移除的元素,结果列表被压缩。也就是说,后面全部元素的索引都被减1
default void replaceAll(UnaryOperator<E> opToApply) 使用opToApply函数获取得的值更新
E set(int index,E obj) 将调用列表中由index指定的索引位置的元素设置为obj,返回原来的值
default void sort(Comparator<? super E> comp) 使用comp指定的比较器排序列表(JDK8新增)
List<E> subList(int start,int end) 返回的子列表包含调用列表中索引位置在start到end-1之间的元素。返回列表中的元素仍然被调用对象引用

   相对于Collection接口定义的add()和addAll()版本,List添加了add(int ,E)和addAll(int,Collection)方法,这些方法在指定的索引位置插入元素。此外,List改变了Collection定义的add(E)和addAll(Collection)方法的语义,以便它们将元素添加到列表的尾部。使用replaceAll()方法能够修改集合中的每一个元素(UnaryOperator是JDK8添加的一个函数式接口)。
   为了得到存储在特定位置的元素,使用对象的索引调用get()方法。为了给列表中的元素赋值,能够调用sat()方法,并指定将要修改的对象的索引。为了查找对象的索引,可使用indexOf()或lastIndexOf()方法。
   经过调用subList()方法,指定子列表的开始索引和结束索引,能够得到列表的子列表。subList()方法极大地简化了列表处理。List定义的sort()方法是排序列表的一种方法。

3.3 Set接口

   Set接口定义了组(set)。它扩展了Collection接口,而且声明了集合中不容许有重复元素的组行为。因此,若是为组添加剧复的元素,add()方法就会返回false。Set接口没有定义本身的其余方法。Set是泛型接口,其声明以下:

interface Set<E>

   其中,E指定了组将包含的对象的类型。

3.4 SortedSet接口

   SortedSet接口扩展了Set接口,而且声明了以升序进行排序的组行为。SortedSet是泛型接口,其声明以下:

interface SortedSet<E>

   其中,E指定了组将包含的对象的类型。
   除了Set提供的那些方法外,SortedSet接口还声明了表4中汇总的方法。若是调用组中没有包含条目,其中的几个方法会抛出NotSuchElementException异常;若是对象和组中的元素不兼容,就抛出ClassCastException异常;若是试图为不容许存储null对象的组添加null对象,就抛出NullPointerException;若是使用的参数无效,会抛出IllegalArgumentException异常。

表4 SortedSet接口声明的方法
方 法 描 述
Comparator<? super E> comparator 返回已排序调用的比较器。若是这个组使用天然排序,就返回null
E first() 返回已排序调用组的第一个元素
SortedSet<E> headSet(E end) 返回的SortedSet对象包含已排序调用组中那些小于end的元素,对于返回的已排序组中的元素,也将被已排序调用组引用
E last() 返回已排序调用组中的最后一个元素
SortedSet<E> subSet(E start,E end) 返回的SortedSet对象包含索引位置在start与end-1之间的元素。返回组中的元素也将被调用对象引用
SortedSet<E> tailSet(E start) 返回的SortedSet对象包含排序组中大于或等于start的元素,返回组中的元素也将被调用对象引用

   SortedSet定义了一些便于进行组处理的方法。为了得到组中的第一个对象,能够调用first()方法;为了获得最后一个元素,可使用last()方法;经过调用subSet()方法能够得到已排序组的子组,其参数指定了子组中的第一个和最后一个对象;若是须要得到从组中第一个元素开始的子组,可使用headSet()方法;若是但愿获得以组的末尾结尾的子组,可使用tailSet()方法。

3.5 NavigableSet接口

   NavigableSet接口扩展了SortedSet接口,而且该接口声明了基于最接近匹配原则检索元素的集合行为。NavigableSet是泛型接口,其声明以下:

interface NavigableSet<E>

   其中,E指定了将包含的对象的类型。除了继承自SortedSet接口的方法外,NavigableSet接口还添加了表5中汇总的方法。若是对象与组中的元素不兼容,会抛出ClassCastException异常;若是试图在不容许存储null对象的组中使用null对象,会抛出NullPointerException异常;若是使用的参数无效,会抛出IllegalArgumentException异常。

表5 NavigableSet接口声明的方法
方 法 描 述
E celling(E obj) 在组中查找大于等于obj的最小元素。若是找到了这样的元素就返回元素;不然返回null
Iterator<E> descendingIterator() 返回一个从最大元素向最小元祖医用的迭代器。换句话说,返回一个反向迭代器
NavigableSet<E> descendingSet() 返回用来翻转调用组的NavigableSet对象。若是找到了这样的元素就返回元素;不然返回null
E floor(E obj) 查找组中小于等于obj的最大元素。若是找到了这样的元素就返回元素;不然返回null
NavigableSet<E> headSet(E upperBound,boolean incl) 返回的NavigableSet对象包含调用组中小于upperBound的全部元素。若是incl为true,那么包含等于upperBound的那个元素。结果组基于调用组
E higher(E obj) 在组中查找大于obj的最大元素。若是找到了这样的元素,就返回元素;不然返回nyll
E lower(E obj) 在组中查找小于obj的最大元素。若是找到了这样的元素,就返回元素;不然返回null
E pollFirst() 返回第一个元素,在操做过程当中移除该元素,由于组是排过序的,因此该元素具备最小值。若是组为空,那么返回null
E pollLast() 返回最后一个元素,在操做过程当中移除该元素。由于组是排过序的,因此该元素具备最大值。若是组为空,那么返回null
NavigableSet<E> subSet(E lowerBound,boolean lowIncl,E upperBound,boolean highIncl) 返回的NavigableSet对象包含调用组中大于lowerBound且小于upperBound的全部元素。若是lowIncl为true,那么包含等于lowerBound的那个元素;若是highIncl为true,那么包含等于upperBound的那个元素。结果组基于调用组
NavigableSet<E> tailSet(E lowerBound,boolean incl) 返回的NavigableSet对象包含调用组中大于lowerBound的全部元素。若是incl为true,那么包含等于lowerBound的那个元素。结果组基于调用组
3.6 Queue接口

   Queue接口扩展了Collection接口,而且声明了队列的行为,队列一般是先进先出的列表。可是,还有基于其余准则的队列类型,其声明以下:

interface Queue<E>

   其中,E指定队列将包含的对象的类型。表6显示了Queue定义的方法。

表6 Queue接口声明的方法
方 法 描 述
E element() 返回队列头部的元素,不移除该元素。若是队列为空,那么抛出NoSuchElementException异常
boolean offer(E obj) 试图将obj添加到队列中。若是将obj添加到队列中,那么返回true;不然返回false
E peek() 返回队列头部的元素。若是队列为空,那么返回null。不移除该元素
E poll() 返回队列头部的元素,在操做过程当中移除该元素。若是队列为空,那么返回null
E remove() 移除队列头部的元素,并在操做过程当中返回该元素。若是队列为空,那么抛出NoSuchElementException异常

   若是对象与队列中的元素不兼容,有些方法会抛出ClassCastException异常;若是试图在不容许有null元素的队列中存储null对象,会抛出NullPointerException异常;若是使用的参数无效,会抛出IllegalArgumentException异常;若是试图从空的队列中移除元素,会抛出NoSuchElementException异常。
   Queue接口尽管比较简单,但却提供了几个有趣的功能。首先,只能从队列的头部移除元素。其次,有两个方法用于获取并移除元素:poll()和remove()。它们之间的区别是:若是队列为空,poll()方法会返回null,而remove()方法会抛出异常。再次,有两个方法能够获取单并不从队列的头部移除元素:element()和peek()。它们之间的惟一区别是:若是队列为空,element()方法会抛出异常,而peek()方法会返回null。最后,注意offer()方法只是试图向队列中添加元素。由于有些队列具备固定长度,而且可能已满,因此offer()方法可能会失败。

3.7 Deque接口

   Deque接口扩展了Queue接口,而且声明了双端队列的行为。双端队列既能够像标准队列那样先进先出,也能够像堆栈那样后进先出。Deque是泛型接口,其声明以下:

interface Deque<E>

   其中,E指定了双端队列将包含的对象的类型。除了继承自Queue接口的方法外,Deque接口还添加了表7中汇总的方法。若是对象与双端队列中的元素不兼容,有些方法会抛出ClassCastException异常;若是试图向不容许为null元素的双端队列中存储null对象,会抛出NullPointerException异常;若是使用的参数无效,会抛出IllegalArgumentException异常;若是想长度固定而且已满的双端队列中添加元素,会炮术IllegalStateException异常;若是试图从空的双端队列中移除元素,会抛出NoSuchElementException异常。

表7 Deque接口声明的方法
方 法 描 述
void addFirst(E obj) 将obj添加到双端队列的头部。若是超出了容量有限的双端队列的空间,就抛出IllegalStateException异常
void addLast(E obj) 将obj添加到双端队列的尾部。若是超出了容量有限的双端队列的空间,就抛出IllegalException异常
Iterator<E> descendingIterator() 返回一个双端队列尾部向头部移动的迭代器。换句话说,返回一个反方向迭代器
E getFirst() 返回双端队列的第一个元素,不从双端队列中移除该对现货。若是双端队列为空,就抛出NoSuchElemetException异常
E getLast() 返回双端队列的最后一个元素,不从双端队列中移除该对象。若是双端队列为空,就抛出NoSuchElementException异常
boolean offerFirst(E obj) 将obj添加到双端队列的头部。若是obj被添加到双端队列中,就返回true;不然返回false。由于,若是试图想一个已满而且容量有限的双端队列中添加obj,那么会返回false
boolean offerLast(E obj) 试图将obj添加到双端队列的尾部,若是obj被添加到双端队列中,就返回true;不然返回false
E peekFirst() 返回双端队列头部的元素。若是双端队列为空,就返回null。对象不被移除
E peekLast() 返回双端队列的尾部元素。若是双端队列为空,就返回null。对象不被移除
E pollFirst() 返回双端队列头部的元素,在操做过程当中移除该元素。若是双端队列为空,就返回null
E pollLast 返回双端队列尾部的元素,在操做过程当中移除该元素,若是双端队列为空,就返回null
E pop() 返回双端队列头部的元素,在操做过程当中移除该元素。若是双端队列为空,就抛出NoSuchException异常
void push(E obj) 将obj添加到双端队列的头部,若是超出了容量有限的双端队列的空间,就抛出IllegalStateException异常
E removeFirst() 返回双端队列头部的元素,在操做过程当中移除该元素。若是双端队列为空,就抛出NoSuchException异常
boolean removeFirstOccurrence(Object obj) 在双端队列中移除第一次出现的obj对象。若是成,就返回true;若是双端队列中不包含obj,就返回false
E removeLast() 返回双端队列尾部的元素,在操做过程当中移除该元素。若是双端队列为空,就抛出NoSuchElementException异常
boolean removeLastOccurence(Object obj) 从双端队列中移除最后一次出现的obj对象。若是成功,就返回true,若是双端队列中不包含obj,就返回false

   注意Deque接口提供了push()和pop()方法。这些方法使得Deque接口的功能与堆栈相似。此外,还应当注意descendingIterator()方法,该方法返回的迭代器以相反的顺序返回元素。换句话说,返回一个从集合的尾部向头部移动的迭代器。能够将Deque接口实现为容量有限的队列,这觉得着只能从双端队列中添加数量有限的元素。若是数量有限,那么当试图向双端队列中添加元素时可能会失败。Deque接口容许以两种方法处理这类失败。首先,若是容量有限的双端队列已满,addFirst()和addLast()这类方法会抛出IllegalStateException异常,其次,若是不能添加元素,offerFirst()和offerLast()方法会返回false。

4 集合类

   如今已经熟悉了集合接口,下面开始分析实现他们的标准类。其中一些类提供了可使用的完整实现。其余一些类是抽象的,它们提供了能够建立具体集合开始点的大致实现。做为通常规则,集合类不是同步的,可是能够得到它们的同步版本。表8汇总了标准集合类。

表8 标准集合类
描 述
AbstractCollection 实现了Collection接口的大部分
AbstractList 扩展AbstractCollection类并实现了List接口的大部分
AbstractQueue 扩展AbstractCollection类并实现了Queue接口的大部分
AbstractSequentialList 扩展AbstractList类,用于顺序访问而不是随机访问集合中的元素
LinkedList 经过扩展AbstractSequentialList类实现链表
ArrayList 经过扩展AbstractList类实现动态数组
ArrayDeque 经过扩展AbstractCollection类并实现Deque接口,实现动态双端队列
AbstractSet 扩展AbstractCollection类并实现Set接口的大部分
EnumSet 扩展AbstractSet类,用于enum元素
HashSet 扩展AbstractSet类,用于哈希表
LinkedHashSet 扩展HashSet类以容许按照插入的顺序进行迭代
PriorityQueue 扩展AbstractQueue类以支持基于优先级的队列
TreeSet 实现存储于树种的组,扩展AbstractSet类

   接下来将分析具体集合类并演示它们的使用
   注意:
   除了集合类以外,还有一些遗留的类,例如Vector、Stack以及HashTable,也进行了从新设计以支持集合。

4.1 ArrayList类(源码分析地址:https://blog.csdn.net/qq_26803795/article/details/104559703)

   ArrayList类扩展了AbstractList类并实现了List接口。ArrayList是泛型类,其声明以下:

class ArrayList<E>

   其中,E指定了列表将包含的对象的类型。
   ArrayList类支持可以按需增加的动态数据,在Java中,标准数组的长度是固定的。数组在建立以后,就不能增加或缩小,这意味着必须事先指定数组将包含多少元素。可是,有时可能直到运行时才知道所需数组的准确大小。为了处理这种状况,集合框架定义了ArrayList类。本质上,ArrayList就是元素为对象引用的长度可变的数组。也就是说,ArrayList能够动态增长或减少大小。数组列表使用初始值大小建立。当超出这个大小时,集合会自动扩大。当对象被移除时,也能够减少数组。
   注意
   遗留类Vector也支持动态数组。
   ArrayList具备以下所示的构造函数:

ArrayList()
ArrayList(Collection<? extends E> c)
ArrayList(int capacity)

   第1个构造函数构建一个空的数组列表。第2个构造函数构建一个数组列表,使用集合c的元素进行初始化。第3个构造函数构建一个初始容量为capacity的数组列表。容量是用于存储元素的数组的大小。当向数组列表中添加元素时,容量会自动增加。
   下面的程序演示了ArrayList类的简单应用。该程序为String类型的对象建立了一个数组列表,而后向其中添加几个字符串(带引号的字符串会被转换为String对象)。而后显示这个列表。移除一些元素以后,再次显示这个列表。

import java.util.ArrayList;
//Demonstrate ArrayList
class ArrayListDemo {
 public static void main(String[] args) {
      //Create an array list.
      ArrayList<String> a1 = new ArrayList<String>();
      System.out.println("Initial size of a1: "+a1.size());
      //Add elements to the array list.
      a1.add("C");
      a1.add("A");
      a1.add("E");
      a1.add("B");
      a1.add("D");
      a1.add("F");
      a1.add(1,"A2");
      System.out.println("Size of a1 after additions: "+a1.size());
      //Display the array list
      System.out.println("Contents od a1: "+a1);
      //Remove elements from the array list
      a1.remove("F");
      a1.remove(2);
      System.out.println("Size of a1 after deletions: "+a1.size());
      System.out.println("Contents of a1: "+a1);
      /* 输出结果: Initial size of a1: 0 Size of a1 after additions: 7 Contents od a1: [C, A2, A, E, B, D, F] Size of a1 after deletions: 5 Contents of a1: [C, A2, E, B, D] */
  }
}

   a1一开始为空,而且当添加元素时会增加。当移除元素时,a1的大小会减少。
   在前面的例子中,使用toString()方法提供的默认转换显示集合的内容,该方法集成自AbstractCollection。尽管对于简单的程序来讲,toString()方法时足够的,可是不多使用它来显示真实集合的内容。一般会提供本身的输出例程。可是,对于接下来的几个例子,toString()方法建立的默认输出是足够的。
   尽管存储对象时,ArrayList对象的容量会自动增加,可是能够调用ensureCapacity()方法以手动增加ArrayList对象的容量。若是事先知道将在集合中存储的元素比当前保存的元素多不少,咱们可能但愿这么作。在开始时,一次性增长容量,从而避免之后屡次从新分配内存。由于从新分配内存很耗时,阻止没必要要的内存分配次数能够提升性能。ensureCapacity()方法的签名以下:

void ensureCapacity(int cap)

   其中,cap指定集合新的最小容量。
   相反,若是但愿减少ArrayList对象数组的大小,进而使其大小精确地等于当前容纳的元素数量,能够调用trimToSize()方法,改方法以下所示:

void trimToSize()

   从ArrayList获取数组
   使用ArrayList时,有时会但愿获取包含列表内容的实际数组。能够调用toArray()方法来完成这一工做,该方法是由Collection接口定义的。因为某些缘由,咱们可能但愿将集合转换为数组,例如:

  • 为特定操做获取更快的处理速度
  • 为方法传递数组,而且方法没有接收集合的重载形式
  • 将基于集合的代码集成到不支持集合的遗留代码中
       无论什么缘由,将ArrayList转换成数组都是很日常的事情。
       前面解释过,toArray()方法有两个版本:
Object[] toArray()
<T> T[] toArray(T array[])

   第一个版本返回元素类型为Object的数组。第二个版本返回元素类型为T的数组。一般,第二个版本更方便,由于能返回恰当类型的数组,下面的程序演示了它的使用:

import java.util.ArrayList;

//Convert an ArrayList into an array.
class ArrayListToArray {
public static void main(String[] args) {
      //Create an array list.
      ArrayList<Integer> a1 = new ArrayList<Integer>();
      //Add elements to the array list.
      a1.add(1);
      a1.add(2);
      a1.add(3);
      a1.add(4);
      System.out.println("Contents of a1: "+a1);
      //Get the array
      Integer ia[] = new Integer[a1.size()];
      ia = a1.toArray(ia);
      int sum = 0;
      //Sum the array.
      for(int i:ia){
          sum += i;
      }
      System.out.println("Sum is: "+sum);
      /** * 輸出 * Contents of a1: [1, 2, 3, 4] * Sum is: 10 */
  }
}

   该程序首先建立一个整数集合。接下来,调用toArray()方法并获取一个Integer数组。而后,使用for-each风格的for循环对数组内容进行求和。
   集合只存储索引,而不能存储基本类型的值。可是,自动装箱使得add()方法传递int类型的值成为可能,而不须要像程序中那样将它们手动封装到一个Integer对象中。自动装箱使得它们被自动封装。经过这种方式自动装箱显著提升了存储基本类型的易用性。

4.2 LinkedList类

   LinkedList类扩展了AbstractSequentialList类,实现了List、Deque以及Queue接口,而且它还提供了一种链表数据结构。LinkedList是泛型类,其声明以下:

class LinkedList<E>

   其中,E指定了链表将包含的对象的类型。LinkedList具备两个构造函数,以下所示:

LinkedList()
LinkedList(Collection<? extends E> c)

   第一个构造函数构建一个空的链表。第二个构造函数构建一个使用集合c的元素进行初始化的链表。
   由于LinkedList实现了Deque接口,因此能够访问Deque定义的方法。例如,要向列表头部添加元素,可使用addFirst()或offerFirst()方法;要向列表尾部添加元素,可使用addLast()或offerLast()方法;为了获取第一个袁旭,可使用getFirst()或peekFirst()方法;为了获取最后一个元素,可使用getLast()或peekLast();为了移除第一个元素,可使用removeFirst()或pollFirst()方法;为了移除最后一个元素,可使用removeLast()或pollLast()方法。、
   下面的程序演示了LinkedList:

import java.util.LinkedList;
//Demonstrate LinkedList.
class LinkedListDemo {
  public static void main(String[] args) {
       //Create a linked list.
       LinkedList<String> l1 = new LinkedList<String>();
       //Add elements to the linked list.
       l1.add("F");
       l1.add("B");
       l1.add("D");
       l1.add("E");
       l1.add("C");
       l1.addLast("Z");
       l1.addFirst("A");
       l1.add(1,"A2");
       System.out.println("Original contents of l1: "+l1);
       //Remove elements from the linked list.
       l1.remove("F");
       l1.remove(2);
       System.out.println("Contents of l1 after deletion: "+l1);
       //Remove first and last elements.
       l1.removeFirst();
       l1.removeLast();
       System.out.println("l1 after deleting first and last: "+l1);
       //Get and set a value.
       String val = l1.get(2);
       l1.set(2,val+" Changed");
       System.out.println("l1 after change: "+l1);
       /** * 输出结果 *Original contents of l1: [A, A2, F, B, D, E, C, Z] * Contents of l1 after deletion: [A, A2, D, E, C, Z] * l1 after deleting first and last: [A2, D, E, C] * l1 after change: [A2, D, E Changed, C] */
   }
}

   由于LinkedList实现了List接口,因此能够调用add(E)方法来向列表尾部追加条目,就像调用addLast()方法那样。为了在指定位置插入条目,可使用add()方法的add(int,E)形式,就像在这个例子中调用add(1,“A2”)那样。
   注意如何经过调用get()和set()方法修改l1中的第三个元素。为了获取元素的当前值。向get()方法传递元素存储位置的索引。若是要为索引指定的元素赋新值,能够向set()方法传递相应的索引和新值。

4.3 HashSet类

   HashSet类扩展了AbstractSet类并实现了Set接口,该类用于建立使用哈希表存储元素的集合。HashSet是泛型类,其声明以下:

class HashSet<E>

   其中,E指定了组将包含的对象的类型。
   哈希表使用称之为三列的机制存储信息。在散列机制中,键的信息用于肯定惟一的值,称为哈希码。而后将哈希码用做索引,在索引位置存储与键关联的数据。将键转换为哈希码是自动执行的——咱们永远不会看淡哈希码自己。此外,咱们的代码不能直接索引哈希表。散列机制的优点是add()、contains()、remove()以及size()方法的执行时间保持布标,及时是对于较大的组也是如此。
   HashSet类定义了如下构造函数:

HashSet()
HashSet(Collection<? extends E> c)
HashSet(int capacity)
HashSet(int capacity,float fillRatio)

   第1种形式构造一个默认的哈希组。第2种形式使用集合c中的元素初始化哈希组。第3种形式将哈希组的容量设置为capacity(默认容量是16)。第4种形式根据参数同时初始化哈希组的容量和填充率(也称载入容量(load capacity))。填充率必须介于0.0到1.0之间,填充率决定了哈希组被填充到什么程度就增长容量。特别地,当元素的数量大于哈希组的容量与填充率的乘积时,将扩展哈希组。对于吗,没有填充率的构造函数,使用0.75做为填充率。
   除了超类和接口提供的方法外,HashSet没有定义任何其余方法。
   HashSet不能保证元素的顺序,注意着一点很重要,由于散列处理过程一般不建立有序的组。若是须要有序地进行存储,那么须要另一个组,TreeSet是一个比较好的选择。
   下面是演示HashSet的一个例子:

import java.util.HashSet;
//Demonstrate HashSet.
class HashSetDemo {
  public static void main(String[] args) {
       //Create a hash set.
       HashSet<String> hs = new HashSet<String>();
       //Add elements to hash set.
       hs.add("Beta");
       hs.add("Alpha");
       hs.add("Eta");
       hs.add("Gamma");
       hs.add("Epsilon");
       hs.add("Omega");
       System.out.println(hs);
       /** * 输出: * [Gamma, Eta, Alpha, Epsilon, Omega, Beta] */
   }
}

   程序的输出正如前面所解释的,元素不是按有序地顺序存储的。

4.4 LinkedHashSet类

   LinkedHashSet扩展了HashSet类,他没有添加本身的方法。LinkedHashSet是泛型类,其声明以下:

class LinkedHashSet<E>

   其中,E指定了组将包含的对象的类型。LinkedHashSet的构造函数与HashSet的构造函数对应。
   LinkedHashSet维护组中条目的一个链表,链表中条目的数据也就是插入它们的顺序,这使得能够按照插入顺序迭代集合。换句话讲,当使用迭代器遍历LinkedHashSet时,元素将以插入它们的顺序返回。这也是在对LinkedHashSet对象调用toString()方法时,在返回的字符串中包含它们的顺序。为了查看LinkedHashSet的效果,尝试在前面的程序中使用LinkedHashSet代替HashSet,输出的是:

[Beta, Alpha, Eta, Gamma, Epsilon, Omega]

   元素输出的顺序就是插入它们的顺序。

4.5 TreeSet类

   TreeSet类扩展了AbstractSet类并实现了NavigableSet接口,用于建立使用树进行存储的组。对象以升序存储,访问和检索速度至关快,这使得对于存储大量的、必须可以快速查找到的有序信息来讲,TreeSet是极佳选择。
   TreeSet是泛型类,其声明以下:

class TreeSet<E>

   其中,E指定了组将包含的对象的类型。
   TreeSet具备以下构造函数

TreeSet()
TreeSet(Collection<? Extends E> c)
TreeSet(Comparator<? super E> comp)
TreeSet(SortedSet<E> ss)

   第1中形式构建一个空树,将按照元素的天然顺序以升序进行存储。第2种形式构建一个包含集合c中元素的树。第3种形式构建一个空树,将按照comp指定的比较器进行存储。第4中形式构建一个包含ss中元素的树。
   下面的程序演示TreeSet类的一个例子:

import java.util.TreeSet;
//Demonstrate TreeSet.
class TreeSetDemo {
  public static void main(String[] args) {
       //Create a tree set.
       TreeSet<String> ts = new TreeSet<String>();
       //Add elements to the tree set.
       ts.add("C");
       ts.add("A");
       ts.add("B");
       ts.add("E");
       ts.add("F");
       ts.add("D");
       System.out.println(ts);
       /** * 输出 * [A, B, C, D, E, F] */
   }
}

   前面解释过,由于TreeSet在树中存储元素,因此它们自动以排过序的顺序进行排序,正如输出所确认的。
   由于TreeSet实现了NavigableSet接口,因此可使用NavigableSet接口定义的方法来检索TreeSet中的元素。例如,对于前面的程序,下面的语句使用subSet()方法获取ts的一个子组,该子组包含从C(包含C)到F(不包含F)的元素,而后显示结果组:
   System.out.println(ts.subSet(“C”,”F”));
   这条语句的输出以下所示:
    [C,D,E]

4.6 PriorityQueue类

   PriorityQueue扩展了AbstractQueue类并实现了Queue接口,用于建立根据队列的比较器来断定优先次序的队列。PriorityQueue是泛型类,其声明以下:

class PriorityQueue<E>

   其中,E指定了队列将存储的对象的类型。PriorityQueue是动态的、按需增加的。PriorityQueue定义了一下7个构造函数:

PriorityQueue()
PriorityQueue(int capacity)
PriorityQueue(Comparator<? super E> comp)(JDK8新增)
PriorityQueue(int capacity,Comparator<? super E> comp)
PriorityQueue(Collection<? extends E> c)
PriorityQueue(PriorityQueue<? extends E> c)
PriorityQueue(SortedSet<? extends E> c)

   第1个构造函数建立一个空队列,起始容量为11。第2个构造函数构建一个具备指定初始容量的队列。第3个构造函数指定了一个比较器,第4个构造函数构建具备指定容量和比较器的队列。最后3个构造函数构建使用参数c传递过来的集合中的元素进行初始化的队列。对于由这些构造函数建立的全部队列,当添加元素时,容量都会自动增加。
   当构建PriorityQueue对象时,若是没有指定比较器,将使用在队列中存储的数据类型的默认比较器。默认比较器以升序队列进行排序。所以,队列头部的条目将包含最小的值。可是,经过提供定值的比较器,能够指定不一样的排序模式。例如,当对时间戳的条目进行排序时,能够优先考虑最先的条目。
   经过调用comparator()方法,能够获取对PriorityQueue使用的比较器的引用,该方法以下所示:

Comparator<? super E> comparator()

   该方法返回比较器。若是为调用队列使用的是天然顺序,那么返回null。
   须要注意的一点:尽管可使用迭代器表里PriorityQueue,可是迭代的顺序是不肯定的。为了正确地使用PriorityQueue,必须调用odder()和poll这类方法,这些方法是由Queue接口定义的。

4.7 ArrayDeque类

   ArrayDeque扩展了AbstractCollection类并实现了Deque接口,没有添加本身的方法。ArrayDeque是动态数组,没有容量限制(Deque接口支持限制容量的实现,可是这种限制不是必需的)。ArrayDeque是泛型类,其声明以下:

class ArrayDeque<E>

   其中,E指定了集合中将存储的对象的类型。
   ArrayDeuqe定义了如下构造函数:

ArrayDeque()
ArrayDeque(int size)
ArrayDeque(Collection<? extends E> c)

   第1个构造函数建立一个空的双端队列,初始容量是16。第2个构造函数构建一个具备指定初始容量的双端队列。第3个构造函数建立使用参数c传递过来的集合的元素进行初始化的双端队列。对于全部这些状况,当向双端队列中添加元素时,容量都会根据须要增加。
   下面的程序演示了ArrayDeque,这里使用它来建立一个堆栈:

import java.util.ArrayDeque;
//Demonstrate ArrayDeque
class ArrayDequeDemo {
  public static void main(String[] args) {
       //Create an array deque.
       ArrayDeque<String> adq = new ArrayDeque<String>();
       //Use an ArrayDeque like a stack.
       adq.push("A");
       adq.push("B");
       adq.push("D");
       adq.push("E");
       adq.push("F");
       System.out.println("Popping the stack: ");
       while(adq.peek()!=null){
           System.out.print(adq.pop()+" ");
       }
       System.out.println();
       /** * 输出: * Popping the stack: * F E D B A */
   }
}
4.8 EnumSet类

   EnumSet扩展了AbstractSet类并实现了Set接口,专门用于枚举类型的元素。EnumSet是泛型类,其声明以下:

class EnumSet<E extends Enum<E>>

   其中,E指定了元素。注意E必须扩展Enum<E>,这强制要求元素必须是指定的枚举类型。
   EnumSet类没有定义构造函数,而是使用表9中显示的工厂方法来建立对象。全部方法均可能抛出NullPointerException异常,copyOf()和range()方法还可能抛出IllegalArgumentException异常。注意of()方法被重载了屡次,这是出于效率方面的考虑。当参数数量比较少时,传递已知数量的参数比使用数量可变的参数会更快一些。

表9 EnumSet类声明的方法
方 法 描 述
static <E extends Enum<E>> EnumSet<E> allOf(Class<E> t) 建立的EnumSet包含由t指定的枚举中的元素
static <E extends Enum<E> complementOf(EnumSet <E> e)> 建立的EnumSet由未存储在e中的元素组成
static <E extends Enum<E> EnumSet copyOf(EnumSet<E> c)> 根据c中存储的元素建立EnumSet
static <E extends Enum<E> EnumSet noneOf(Class<E> t)> 建立的EnumSet不包含由t指定的枚举中的元素,根据定义,这是一个空组
static <E extends Enum<E> EnumSet of(E v,E …varargs)> 建立的EnumSet包含v,以及0个或更多个其余枚举
static <E extends Enum<E> EnumSet of(E v)> 建立的EnumSet包含v
static <E extends Enum<E> EnumSet of(E v1,E v2)> 建立的EnumSet包含v1和v2
static <E extends Enum<E> EnumSet of(E v1,E v2,E v3)> 建立的EnumSet包含v一、v2和v3
static <E extends Enum<E> EnumSet of(E v1,E v2,E v3,E v4)> 建立的EnumSet包含v一、v二、v3和v4
static <E extends Enum<E> EnumSet of(E v1,E v2,E v3,E v4,E v5)> 建立的EnumSet包含v一、v二、v三、v4和v5
static <E extends Enum<E> EnumSet range(E start,E end)> 建立的EnumSet包含指定范围(由start和end指定)内的元素

5 经过迭代器访问集合

   一般,咱们但愿遍历集合中的元素。例如,可能但愿显示每一个元素。完成这一步工做的方法之一是使用迭代器,迭代器是实现了Iterator或ListIterator接口的对象。Iterator接口容许遍历集合,获取或删除元素。ListIterator接口扩展了Iterator接口,容许双向遍历列表,而且容许修改元素。Iterator和ListIterator是泛型接口,它们的声明以下:

interface Iterator<E>
interface ListIterator<E>

   其中,E指定了将被迭代的对象的类型。表10显示了Iterator接口声明的方法。表11显示了ListIterator接口声明的方法(以及从Iterator集成的方法)。对于这两种强开,修改集合的操做都是可选的。例如,当使用只读的集合时,remove()方法会抛出UnsupportedOperationException异常。其余各类异常都是可能发生的。

表10 Iterator接口声明的方法
方 法 描 述
default void forEachRemaining(Consumer<? super E> action) 对于集合中每一个未处理的元素,执行action指定的动做(JDK8新增)
boolean hasNext() 若是还有更多的元素,就返回true;不然返回false
E next() 返回下一个元素。若是不存在下一个元素,就抛出NoSuchElementException异常
default void remove() 移除当前元素。若是在调用next()方法以前试图调用remove()方法,就会抛出IllegalStateException异常。默认版本抛出UnsupportedOperationException异常
表11 ListIterator接口声明的方法
方 法 描 述
void add(E obj) 将obj插入到列表中,新插入的元素位于下一次next()方法调用返回的元素以前
default void forEachRemaining(Consumer<? super E> action) 对于集合中每一个未处理的元素,执行action指定的动做(JDK8新增)
boolean hasNext() 若是存在下一个元素,就返回true;不然返回false
boolean hasPrevious() 若是存在前一个元素,就返回true;不然返回false
E next() 返回下一个元素。若是不存在下一个元素,就抛出NoSuchElementException异常
int nextIndex() 返回下一个元素的索引,若是不存在下一个元素, 就返回列表的大小
E previous() 返回前一个元素。若是不存在前一个元素,就抛出NoSuchElementException异常
int previousIndex() 返回前一个元素的索引。若是不存在前一个元素,就返回-1
void remove() 从列表中移除当前元素。若是在调用next()或previous()方法以前调用remove()方法,那么会抛出IllegalStateException异常
void set(E obj) 将obj的值赋值给当前元素,也就是next()或previous()方法调用最后返回的元素

   注意
   从JDK8开始,也可使用Spliterator循环遍历集合。Spliterator的工做方式与Iterator不一样。

5.1 使用迭代器

   为了可以经过迭代器访问集合,首先必须得到迭代器。每一个集合类都提供了iterator()方法,改方法返回一个指定集合开头的迭代器。经过使用这个迭代器对象,能够访问集合中的每一个元素,每次访问一个元素。一般,为了使用迭代器遍历集合的内容,须要如下步骤:
   (1) 经过调用集合的iterator()方法,获取指向集合开头的迭代器
   (2) 创建一个hasNaxt()方法调用循环。只要hasNext()方法返回true,就继续迭代。
   (3) 在循环中,经过调用next()方法获取每一个元素。
   对于实现了List接口的集合,还能够调用listIterator()方法以获取迭代器。如前所述,列表迭代器提供了向前和向后两个方向访问集合的能力,而且容许修改元素。除此以外,ListIterator与Iterator的用法相似。
   下面的例子实现了这些步骤,同时演示了Iterator和ListIterator接口。该例使用一个ArrayList对象,可是通常原则能够应用于任何类型的集合。固然,只有实现了List接口的集合才能使用ListIterator。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.ListIterator;
//Demonstrate iterators.
class IteratorDemo {
 public static void main(String[] args) {
      //Create an array list
      ArrayList<String> a1 = new ArrayList<String>();
      //Add elements to the array list.
      a1.add("C");
      a1.add("A");
      a1.add("E");
      a1.add("B");
      a1.add("D");
      a1.add("F");
      //Use iterator to display contents os a1.
      System.out.print("Original contents of a1: ");
      Iterator<String> itr = a1.iterator();
      while (itr.hasNext()){
          String element = itr.next();
          System.out.print(element + " ");
      }
      System.out.println();
      //Modify objects being iterated.
      ListIterator<String> litr = a1.listIterator();
      while (litr.hasNext()){
          String element = litr.next();
          litr.set(element + "+");
      }
      System.out.print("Modified contents of a1: ");
      itr = a1.iterator();
      while (itr.hasNext()){
          String element = itr.next();
          System.out.print(element + " ");
      }
      System.out.println();
      //Now,display the list backwards
      System.out.print("Modified list backwards: ");
      while (litr.hasPrevious()){
          String element = litr.previous();
          System.out.print(element + " ");
      }
      System.out.println();
      /** * 输出: * Original contents of a1: C A E B D F * Modified contents of a1: C+ A+ E+ B+ D+ F+ * Modified list backwards: F+ D+ B+ E+ A+ C+ */
  }
}

   请特别注意反向显示列表的方式。在以正向显示修改过的列表以后,litr指向列表的末端(请记住,当到达列表末端时,litr.hasNext()方法返回false)。为了能反向遍历列表,程序继续使用litr,可是此次检查是否存在前一个元素。只要存在前一个元素,就获取该元素并显示。

5.2 使用for-each循环替代迭代器

   若是不修改集合内容,也不用反向获取元素,那么使用for-each风格的for循环遍历集合一般比使用迭代器更方便。请记住,可使用for循环遍历任何实现了Iterator接口的集合对象。由于全部集合类都实现了Iterator接口,因此均可以使用for循环操做。
   下面的例子使用for循环对某个集合的元素求和:

import java.util.ArrayList;
//Use the for-each for loop to cycle through a collection.
class ForEachDemo {
  public static void main(String[] args) {
      //Create an array list for integers.
      ArrayList<Integer> vals = new ArrayList<Integer>();
      //Add values to the array list.
      vals.add(1);
      vals.add(2);
      vals.add(3);
      vals.add(4);
      vals.add(5);
      //User for loop to display the values.
      System.out.print("Contents of vals: ");
      for (int v : vals) {
          System.out.print(v + " ");
      }
      System.out.println();
      //Now,sum the values by using a for loop.
      int sum = 0;
      for (int v : vals) {
          sum += v;
      }
      System.out.println("Sum of values: " + sum);
      /** * 输出: * Contents of vals: 1 2 3 4 5 * Sum of values: 15 */
  }
}

   能够看出,与基于迭代器的方法相比,使用for循环更短也更简单。然而,这种方法只能向前遍历集合,而且不能修改集合的内容。

6 Spliterator

   JDK8新增了一种叫作spliterator的迭代器,这种迭代器由Spliterator接口定义。Spliterator用于循环遍历元素序列,在这一点上与刚才介绍过的迭代器相似。可是,使用spliterator的方法与使用迭代器不一样。另外,它提供的功能远比Iterator或ListIterator多。可能对于Spliterator来讲,最重要的一点是它支持并行迭代序列的一部分。Spliterator支持并行编程。而后,即便用不到并行编程,也可使用Spliterator。这么作的一个理由是它将hasNext和next操做合并到了一个方法中,从而提升了效率。
   Spliterator是一个泛型接口,其声明以下所示:

interface Spliterator<T>

   其中,T是被迭代的元素的类型。Spliterator声明了表12中的方法。

表12 Spliterator接口声明的方法
方 法 描 述
int characteristics() 返回调用spliterator的特征,该特征被编码为整数
long estimateSize() 估计剩余的要迭代的元素数,并返回结果。若是因为某种缘由得不到元素数,就返回Long.Max_VALUE
defalut void forEachRemaining(Comsumer<? super T> action) 将action应用到数据源中未被处理的每一个元素
defualt Comparator<? super T> getComparator() 返回调用spliterator使用的比较器;若是使用了天然顺序,就返回null,若是序列未被排序,就抛出IllegalStateException异常
defalut long getExactSizeIfKnow() 若是调用spliterator是SIZED,就返回剩余的要迭代的元素数;不然返回false
default boolean hasCharacteristics(int val) 若是val中传递了调用spliterator的特征,就返回true;不然返回false
boolean tryAdvance(Comsumer<? super T> action) 在迭代中的下一个元素上执行action。若是有下一个元素就返回true;不然返回false
Spliterator<T> trySplit() 若是能够,分割调用spliterator,并返回对分割后新spliterator的引用;失败时,返回false。所以,在操做成功时,原spliterator会迭代序列的一部分,返回的spliterator迭代序列的其余部分

   将Spliterator用于基本迭代任务:只需调用tryAdvance()方法,直到其返回false。若是要为序列中的每一个元素应用相同的动做,forEachRemaining()提供了一种高效的替代方法。对于这两个方法,在每次迭代中将发生的动做都由Consumer对象对每一个元素执行的操做定义。Consumer是一个函数式接口,向对象应用一个动做。它是java.util.function中声明的一个泛型函数式接口。Consumer仅指定了一个抽象方法accept(),以下所示:

void accept(T objRef)

   对于tryAdvance(),每次迭代会将序列中的下一个元素传递给objRef。一般,实现Consumer最简单的方式是使用lambda表达式。
   下面的程序给出了Spliterator的一个简单示例。注意,这个程序同时演示了tryAdvance()和forEachRamaining()。另外,还要注意这些方法如何把Iterator的next()和hasNext()方法的操做合并到一个调用中:

import java.util.ArrayList;
import java.util.Spliterator;
//A simple Spliterator demonstration.
class SpliteratorDemo {
  public static void main(String[] args) {
      //Create an array list for doubles.
      ArrayList<Double> vals = new ArrayList<Double>();
      //Add values to array list.
      vals.add(1.0);
      vals.add(2.0);
      vals.add(3.0);
      vals.add(4.0);
      vals.add(5.0);
      //Use tryAdvance() to display contents of vals.
      System.out.print("Contents of vals:\n");
      Spliterator<Double> spltitr = vals.spliterator();
      while (spltitr.tryAdvance((n) -> System.out.println(n))) ;
      System.out.println();
      //Create new list that contains square roots.
      spltitr = vals.spliterator();
      ArrayList<Double> sqrs = new ArrayList<Double>();
      while (spltitr.tryAdvance((n)->sqrs.add(Math.sqrt(n))));
      //Use forEachRemaining() to display contents of sqrs.
      System.out.print("Contents of sqrs:\n");
      spltitr = sqrs.spliterator();
      spltitr.forEachRemaining((n)-> System.out.println(n));
      /** * 输出 * Contents of vals: * 1.0 * 2.0 * 3.0 * 4.0 * 5.0 * * Contents of sqrs: * 1.0 * 1.4142135623730951 * 1.7320508075688772 * 2.0 * 2.23606797749979 */
  }
}

   虽然这个程序展现了Spliterator的原理,却没有展示其强大的能力。如前所述,在涉及并行处理的地方,Spliterator的最大优点才能体现出来。
   在表12中,注意characteristics()和hasCharacteristics()方法。每一个Spliterator对象都关联着一组叫作特征(characteristics)的特性。这些特性由Spliterator中的静态int域变量定义,如SORTED、DISTINCT、SIZED和IMMUTABLE等。经过调用characteristics(),能够获取这些特征。经过调用hasCharacteristics(),能够肯定调用Spliterator对象是否具备某个特征,通常不须要访问Spliterator的特征,可是有些时候,它们可以帮助建立高效的、健壮的代码。
   Spliterator中包含几个嵌套子接口,它们是针对基本类型double、int和long设计的,分别叫作Spliterator.ofDouble、Spliterator.OfInt和Spliterator.OfLong。还有一个通常化的版本,叫作Spliterator.OfPrimitive(),它提供了更大的灵活性,并做为上述接口的超接口。

7 在集合中存储用户定义的类

   为了简化,前面的例子在集合中存储的是内置对象,例如String或Integer。固然,没有限制集合只能存储内置对象。事实彻底相反,集合能够存储任何类型的对象,包括建立的类对象。例如,分析下面的例子,该例使用LinkedList存储邮件地址:

import com.sun.jna.WString;
//A simple mailing list example.
class Address {
    private String name;
    private String street;
    private String city;
    private String state;
    private String code;

    public Address(String name, String street, String city, String state, String code) {
        this.name = name;
        this.street = street;
        this.city = city;
        this.state = state;
        this.code = code;
    }
    @Override
    public String toString() {
        return name + "\n" + street + "\n" + city + " " + state + " " + code;
    }
}
import java.util.LinkedList;
class MailList {
 public static void main(String[] args) {
      LinkedList<Address> m1 = new LinkedList<Address>();
      //Add elements to the linked list.
      m1.add(new Address("J.W. West", "11 Oak Ave", "Urbana", "IL", "61801"));
      m1.add(new Address("Ralph Baker", "1142 Maple Lane", "Mahomet", "IL", "61853"));
      m1.add(new Address("Tom Carlton", "867 Elm St", "Champaign", "IL", "61820"));
      //Display the mailing list.
      for (Address element : m1) {
          System.out.println(element + "\n");
      }
      /** * 输出: * J.W. West * 11 Oak Ave * Urbana IL 61801 * * Ralph Baker * 1142 Maple Lane * Mahomet IL 61853 * * Tom Carlton * 867 Elm St * Champaign IL 61820 */
  }
}

   除了在集合中存储用户定义的类以外,对于前面的程序,须要重点注意的另一点是:这个程序至关短。当考虑到程序只是有大约50行代码,就构建了一个可以存储、检索以及处理邮件地址的链表时,集合框架的功能便开始显现出来了。若是全部这些功能都必须手动编写的话,程序会长好几倍。集合为大量编程问题提供了现成的解决方案。只要存在现成的解决方案,就应当使用它们。

8 RandomAccess接口

   RandomAccess接口不包含成员。然而,经过实现这个接口,可代表支持高效地随机访问其中的元素。尽管集合可能支持随机访问,可是可能没有如此高效。经过检查RandomAccess接口,客户端能够在运行时肯定集合是否适合特定类型的随机访问操做——特别是当将它们应用于大的集合时(可使用instanceof来断定是否实现了某个接口)。ArrayList和遗留的Vector类实现了RandomAccess接口。

9 使用映射

   映射是存储键和值之间关联关系(键值对)的对象。给定一个键,就能够找到对应的值。键和值都是对象。键必须惟一,可是值能够重复。某些映射能够接受null键和null值,其余映射则不能。
   关于映射须要关注的关键一点是:它们没有实现Iterable接口。这意味着不能使用for-each风格的for循环遍历映射。此外,不能为映射获取迭代器。可是,正如即将看到的,能够获取映射的集合视图,集合视图容许使用for循环和迭代器。

9.1 映射接口

   由于映射接口定义了映射的特性和本质,因此首先从它们开始讨论映射。表13中的接口支持映射。

表13 支持映射的接口
接 口 描 述
Map 将惟一键映射到值
Map.Entry 描述映射中的元素(键/值对),这是Map得内部类
NavigableMap 扩展SortedMap接口,以处理基于最接近匹配原则的键/值对检索
SortedMap 扩展Map接口,从而以升序保存键

   接下来依次介绍这些接口。
   1.Map接口
   Map接口将惟一键映射到值。键是之后用于检索值的对象。给定键和值,能够在Map对象中存储值;存储值之后,可使用相应的键检索值。Map是泛型接口,其声明以下:

interface Map<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   表14总结了Map接口定义的方法。当对象与映射中的元素不兼容时,有些方法会抛出ClassCastException异常;若是试图修改不容许修改的映射,会抛出UnsupportedOperationException异常;若是使用的参数非法,会抛出IllegalArgumentException异常。

表14 Map接口声明的方法
方 法 描 述
void clear() 移除调用映射中的全部键/值对
default V compute(K k,BiFunction<? super K,? super V,? extends V> func) 调用func以构造一个新值。若是func的返回值不是null,就把新的键值对放到映射中,移除原来的配对,并返回新值。若是func返回null,就移除原来的配对,并返回null(JDK8新增)
default V computeIfAbsent(K k,Function<? super K,? extends V> func) 返回与k关联的值。若是没有值,就经过调用func构造一个值,并把该配对输入到映射中,返回构造的值,若是没法构造新值,就返回null(JDK8 新增)
default V computeIfPresent(K k,BiFunction<? super K,? super V,? extends V> func) 若是k包含在映射中,就经过调用func为其构造一个新值,替换映射中原来的值,而后返回新值。若是func返回的值为null,就从映射中删除现有的键和值,并返回null(JDK8新增)
boolean containsKey(Object k) 若是调用映射包含k做为键,就返回true,不然返回false
boolean containsValue(Object v) 若是映射包含v做为值,就返回true;不然返回false
Set<Map.Entry<K,V>> entrySet() 返回包含映射中全部条目的Set对象,这个组包含Map.Entry类型的对象。所以,改方法提供了调用映射的一个组视图
boolean equals(Object obj) 若是obj是Map对象而且与调用映射包含相同的条目,就返回true;不然返回false
default void forEach(BiConsumer<? super K,? super V> action) 对调用映射中的每一个元素执行action。若是在操做过程当中移除了元素,会抛出ConcurrencyModificationException异常(JDK8新增)
V get(Object K) 返回与键k关联的值。若是没有找到键,就返回null
default V getOrDefault(Object k,V defVal) 若是映射中包含与k关联的值,就返回该值;不然,返回defVal(JDK8新增)
int hashCode() 返回调用映射的散列码
boolean isEmpty() 若是调用映射为空,就返回true;不然返回false
Set<K> keyset() 返回包含映射中某些键的Set对象。这个方法提供了调用映射中键的一个组视图
default V merge(K k,V v,BiFunction<? super K,? super V,? extends V> func) 若是k没有包含在映射中,就把k和v配对,添加到映射中,并返回v。不然,func基于原有的值返回一个新值,键被更新为使用这个新值,而且merge()方法返回这个值。若是func返回的值为null,就从映射中删除现有的键和值,并返回null(JDK8新值)
V put(K k,V v) 将一个条目放入调用映射中,覆盖以前与此键关联的值。键和值分别为k和v。若是键不存在就返回null,不然返回以前与键关联的值
void putAll(Map<? extends K,? extends V> m) 将m中的全部条目添加到调用映射中
default V putIfAbsent(K k,V v) 若是此键值对没有包含在调用映射中,或者现有的值为null,就将此配对添加到调用映射中,并返回原来的值。若是以前不存在映射,或者值为null,就返回null(JDK8新增)
V remove(Object k) 移除键等于k的条目
default boolean remove(Object k,Object v) 若是k和v指定的键值对包含在调用映射中,就移除该配对,并返回true,不然返回false(JDK8新增)
default boolean replace(K k,V oldV,V newV) 若是k和oldV指定的键值对包含在调用映射中,就用newV替换oldV,并返回true。不然,返回false(JDK8新增)
default void replaceAll(BiFunction<? super K,? super V,?extends V func>) 对调用映射的每一个元素执行func,用func返回的结果替换元素。若是在操做过程当中删除了元素,会抛出ConcurrentModificationException异常(JDK8新增)
int size() 返回映射中键/值对的数量
Collection<V> values() 返回包含映射中全部值的集合。改方法提供了调用映射中的一个集合视图

   映射围绕两个基本方法:get()和put()。为了将值放入映射中,使用put()方法,指定键和值。为了获取值,调用get()方法,传递键做为参数,值会被返回。
   前面提到过,尽管映射是结合框架的一部分,但映射不是集合,由于没有实现Collection接口。可是,能够得到映射的集合视图。为此,可使用entrySet()方法。该方法返回一个包含映射中元素的Set对象。为了获取键的集合视图,使用keySet()方法;为了获得值的集合视图,使用values()方法。对于这3个集合视图,集合都是基于映射的。修改其中的一个集合会影响其余集合。集合视图是将映射集成到更大的集合框架中手段。
   2.SortedMap接口
   SortedMap接口扩展了Map接口,确保条目以键的升序保存。SortedMap是泛型接口,其声明以下:

interface SortedMap<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   表15总结了SortedMap接口声明的方法。若是在调用映射中没有元素,有些方法会抛出ClassCastException异常;若是对象与映射中的元素不兼容,会抛出ClassCastException异常;若是试图为不容许使用null对象的映射使用null对象,会抛出NullPointerException异常;若是使用的参数无效,会抛出IllegalArgumentException异常。

表15 SortedMap接口声明的方法
方 法 描 述
Comparator<? super K> comparator() 返回调用的有序映射的比较器。若是调用映射使用的是天然排序,就返回null
K firstKey() 返回调用映射中的第一个键
SortedMap<K,V> headMap(K end) 返回由键小于end的那些映射条目组成的有序映射
K lastKey() 返回调用映射中的最后一个键
SortedMap<K,V> subMap 返回由键大于或等于start,但小于end的那些条目组成的有序映射
SortedMap<K,V> tailMap(K start) 返回由键大于或等于start的那些条目组成的有序映射

   有序映射支持很是高的子映射(即映射的子集)操做。为了获取子映射,可使用headMap()、tailMap()或subMap()方法。这些方法返回的子映射是基于调用映射的。修改其中的一个集合会影响其余集合。为得到组中的第一个键,可调用firstKey()方法;为得到最后一个键,可调用lastKey()方法。
   3.NavigableMap接口
   NavigableMap接口扩展了SortedMap接口,支持基于最接近匹配原则的条目检索行为,即支持检索与给定的一个或多个键最想匹配的条目。NavigableMap是泛型接口,其声明以下:

interface NavigableMap<K,V>

   其中,K指定了键的类型,V指定了与键关联的值的类型。除了继承自SortedMap接口的方法外,NavigableMap接口还添加了表16中总结的方法。若是对象与映射中的键不兼容,有些方法会抛出ClassCastException异常;若是试图对不容许使用null对象和null键的集合使用它们,会抛出NullPointerException异常;若是使用的参数无效,会抛出IllegalArgumentException异常。

表16 NavigableMap接口声明的方法
方 法 描 述
Map.Entry<K,V> ceillingEntry(K obj) 搜索映射,查找大于等于obj的最小键。若是找到这样的键就返回与之对应的条目;不然返回null
K ceillingKey(K obj) 搜索映射,查找大于等于obj的最小建。若是找到这样的键,就返回该键;不然返回null
NavigableSet<K> descendingKeySet() 返回的NavigableSet组以逆序形式包含调用映射中的全部键。所以,该方法返回键的逆序组视图。结果基于映射
NavigableMap<K,V> descendingMap() 返回的NavigableMap映射是调用映射的逆序映射。结果映射基于调用映射
Map,Entry<K,V> firstEntry() 返回映射中的第一个条目,也就是具备最小键的条目
Map.Entry<K,V> floorEntry(K obj) 搜索映射,查找小于等于obj的最大键。若是找到这样的键,就返回与之对应的条目,不然返回null
K floorKey(K obj) 搜索映射,查找小于obj的最大键。若是找到这样的键,就返回该键;不然返回null
NavigableMap<K,V> headMap(K upperBound,boolean incl) 返回的NavigableMap映射包含调用映射中,键小于upperBound的全部条目。若是incl为true,那么包含键等于upperBound的那个元素。结果映射基于调用映射
Map.Entry<K,V> higherKey(K obj) 搜索组,查找大于obj的最小建。若是找到这样键,就返回该键;不然返回null
Map.Entry<K,V> lastEntry() 返回映射中的最后一个条目,也就是具备最大键的条目
Map.Entry<K,V> lowerEntry(K obj) 搜索组,查找小于
obj的最大键。若是找到这样的键,就返回与之对应的条目;不然返回null
K lowerKey(K obj) 搜索组,查找小于obj的最大键。若是找到这样的键,就返回该键;不然返回null
NavigableSet<K> navigableKeySet() 返回包含调用映射中全部键的NavigableSet组,结果组基于调用映射
Map.Entry<K,V> pollFirstEntry() 返回第一个条目,在这个过程当中移除该条目。由于映射是排过序的,因此也就是具备最小键值的条目。若是映射为空,就返回null
Map.Entry<K,V> pollLastEntry() 返回最后一个条目,在这个过程当中移除该条目。由于映射是排过序的,因此也就是具备最大键值的条目。若是映射为空,就返回null
NavigableMap<K,V> subMap(K lowerBound,boolean lowIncl,K upperBound,boolean highIncl) 返回的NavigableMap映射包含调用映射中、键大于lowerBound且小于upperBound的全部条目。若是lowIncl为true,那么包含键等于lowerBound的那个元素;若是highIncl为true,那么包含键等于upperBound的那个元素。结果映射等于调用映射
NavigableMap<K,V> tailMap(K lowerBound,boolean incl) 返回的NavigableMap映射包含调用映射中、键大于lowerBound的全部条目。若是incl为true,那么包含键等于lowerBound的那个元素。结果映射基于调用映射

   4.Map.Entry接口
   Map.Entry接口提供了操做映射条目的功能。请记住,Map接口声明的entrySet()方法返回一个包含映射条目的Set对象。组的全部元素都是Map.Entry对象。Map.Entry是泛型接口,其声明以下:

interface Map.Entry<K,V>

   其中,K指定了键的类型,V指定了值的类型。表17总结了Map.Entry接口声明的非静态方法。JDK8为其添加了两个静态方法:comparingByKey()和comparingByValue()。前者返回根据键比较条目的Comparator对象,后者返回根据值比较条目的Comparator对象。

表17 Map.Entry接口声明的方法
方 法 描 述
boolean equals(Object obj) 若是obj是一个键和值都与调用对象相等的Map.Entry,就返回true
K getKey() 返回该映射条目的键
V getValue() 返回该映射条目的值
int hashCode() 返回该映射条目的散列码
V setValue(V v) 将这个映射条目的值设置为v。若是对于该映射v不是正确的类型就抛出ClassCastException异常;若是v存在问题,那么就抛出IllegalArgumentException异常;若是v为null但映射不容许null键,那么抛出NullPointerException异常;若是映射不容许修改,那么抛出UnsupportedOperationException异常
9.2 映射类

   有几个类实现了映射接口,表18总结了能够用于映射的类。

表18 实现了映射接口的类
描述
AbstractMap 实现了Map接口的大部分
EnumMap 扩展了AbstractMap,以使用enum键
HashMap 扩展了AbstractMap,以使用哈希表
TreeMap 扩展了AbstractMap,以使用树结构
WeakHashMap 扩展了AbstractMap,以使用带有弱键的哈希表
LinkedHashMap 扩展了HashMap,以容许按照插入顺序进行迭代
IdentityHashMap 扩展了AbstractMap,而且当比较文档时使用引用相等性

   注意AbstractMap是全部具体映射实现的超类。
   WeakHashMap实现了使用“弱键”的映射,对于这种映射中的元素来讲,键在再也不使用时能够被垃圾回收。
   1.HashMap类
   HashMap扩展了AbstractMap类并实现了Map接口。它使用哈希表存储映射,这使得即便对于比较大的集合,get()和put方法的执行时间也保持不变。HashMap是泛型类,其声明以下:

class HashMap<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   HashMap类定义了如下构造函数:

HashMap()
HashMap(Map<? extends K,? extends V> m)
HashMap(int capacity)
HashMap(int capacity,float fillRatio)

   第1种形式构造了一个默认的哈希映射。第2种形式使用m中的元素构造初始化哈希映射。第3种形式将哈希映射的容量初始化capacity。第4中形式使用参数同时初始化容量和填充率。容量和填充率的含义与前面描述的HashSet相同。默认容量是16,默认填充率是0.75。
   HashMap实现了Map接口并扩展了AbstractMap类,但没有添加任何本身的方法。
   应当注意哈希映射不保证元素的顺序。因此,向哈希映射添加元素顺序不必定是经过迭代器读取它们的顺序。
   下面的程序演示了HashMap,将名字映射到帐户金额。注意获取和使用组视图的方式:

import java.util.HashMap;
import java.util.Map;
import java.util.Set;
class HashMapDemo {
 public static void main(String[] args) {
      //Create a hash map.
      HashMap<String, Double> hm = new HashMap<String, Double>();
      //Put elements to the map
      hm.put("John Doe", new Double(3434.34));
      hm.put("Tom Smith", new Double(123.22));
      hm.put("Jane Baker", new Double(1378.00));
      hm.put("Tod Hall", new Double(99.22));
      hm.put("Ralph Smith", new Double(-19.08));
      //Get a set of entries.
      Set<Map.Entry<String, Double>> set = hm.entrySet();
      //Display the set.
      for (Map.Entry<String, Double> me : set) {
          System.out.print(me.getKey() + ": ");
          System.out.println(me.getValue());
      }
      System.out.println();
      //Deposit 1000 into John Doe's account.
      double balance = hm.get("John Doe");
      hm.put("John Doe", balance + 1000);
      System.out.println("John Doe's new balance: " + hm.get("John Doe"));
      /** * 执行结果(精确的顺序可能会有所变化): * Tod Hall: 99.22 * John Doe: 3434.34 * Ralph Smith: -19.08 * Tom Smith: 123.22 * Jane Baker: 1378.0 * * John Doe's new balance: 4434.34 */
  }
}

   该程序首先建立一个哈希映射,而后添加从名字到帐户余额的映射。接下来,调用entrySet()方法以获取试图组,并使用视图组显示映射的内容。经过调用Map.Entry定义的getKey()和getValue()方法显示键和值的内容。请密切注意存款放入John Doe帐户中的方式。put()方法使用新值自动替换以前与特定键关联的全部值。所以,在更新了John Doe帐户后,哈希表映射仍然值包含"John Doe"帐户。
   2.TreeMap类
   TreeMap扩展了AbstractMap类并实现了NavigableMap接口,该类用于建立存储在树结构中的映射。TreeMap提供了有序存储键/值对的高效手段,并支持快速检索。应当注意,与哈希映射不一样,树映射确保元素以键升序存储。TreeMap是泛型类,其声明以下:

class TreeMap<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   TreeMap类定义了如下构造函数:

TreeMap()
TreeMap(Comparator<? super K> comp)
TreeMap(Map<? extends K,? extends V> m)
TreeMap(SortedMap<K,? extends V> sm)

   第1种形式构造一个空的树映射,使用键的天然顺序进行存储。第2种形式构造一个空的基于树的映射,使用比较器comp进行排序。第3种形式使用m中的条目初始化树映射,使用键的天然顺序进行排序。第4中形式使用sm中的条目初始化树映射,使用与sm相同的顺序进行排序。
   TreeMap类的映射方法没有超出NavigableMap接口和AbstractMap类定义的那些方法。
   下面的程序对前面的程序进行了修改,以使用TreeMap类:

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
class TreeMapDemo {
  public static void main(String[] args) {
      //Create a tree map.
      TreeMap<String, Double> hm = new TreeMap<String, Double>();
      //Put elements to the map
      hm.put("John Doe", new Double(3434.34));
      hm.put("Tom Smith", new Double(123.22));
      hm.put("Jane Baker", new Double(1378.00));
      hm.put("Tod Hall", new Double(99.22));
      hm.put("Ralph Smith", new Double(-19.08));
      //Get a set of entries.
      Set<Map.Entry<String, Double>> set = hm.entrySet();
      //Display the elements.
      for (Map.Entry<String, Double> me : set) {
          System.out.print(me.getKey() + ": ");
          System.out.println(me.getValue());
      }
      System.out.println();
      //Deposit 1000 into John Doe's account.
      double balance = hm.get("John Doe");
      hm.put("John Doe", balance + 1000);
      System.out.println("John Doe's new balance: " + hm.get("John Doe"));
      /** * 执行结果 * Jane Baker: 1378.0 * John Doe: 3434.34 * Ralph Smith: -19.08 * Tod Hall: 99.22 * Tom Smith: 123.22 * * John Doe's new balance: 4434.34 */
  }
}

   注意,TreeMap对键进行排序。然而在这个例子中,它们根据名(first name)而不是根据姓(last name)进行排序。可是,在建立映射时能够经过制定比较器来改变行为。
   3.LinkedHashMap类
   LinkedHashMap扩展了HashMap类,在映射中以插入条目的顺序维护一个条目链表,从而能够按照插入顺序迭代整个映射。
   也就是说,当遍历LinkedHashMap的集合视图时,将元素的插入顺序返回元素。也能够按照最后访问的顺序返回元素的LinkedHashMap。LinkedHashMap是泛型类,其声明以下:

class LinkedHashMap<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   LinkedHashMap定义了如下构造函数:

LinkedHashMap()
LinkedHashMap(Map<? extends K,? extends V> m)
LinkedHashMap(int capacity)
LinkedHashMap(int capacity,float fillRatio)
LinkedHashMap(int capacity,float fillRatio,boolean Order)

   第1种形式构造默认的LinkedHashMap。第2种形式使用m中的元素初始化LinkedHashMap。第3种形式初始化容量。第4种形式同时初始化容量和填充率。容量和填充率的含义与HashMap相同。默认容量为16,默认填充率为0.75。最后一种形式容许指定是按照插入顺序仍是按照最后访问的顺序存储元素。若是Order为true,就使用访问顺序;若是Order问false,就使用插入顺序。
   除了继承自HashMap定义的那些方法,LinkedHashMap只添加了一个方法。这个方法是removeEldestEntry(),以下所示:

protected boolean removeEldestEntry(Map.Entry<K,V> e)

   这个方法由put()和putAll()调用。最旧的条目是由e传入的。默认状况下,这个方法返回false,而且不执行任何操做。可是,若是重写这个方法,就可使LinkedHashMap移除映射中最旧的条目。为此,重写版本须要返回true。要保留最旧的条目,能够返回false。
   4.IdentityHashMap类
   IdentityHashMap扩展了AbstractMap类并实现了Map接口。除了当比较元素时使用引用相等性以外,其余方面与HashMap相似。IdentityHashMap是泛型类,其声明以下:

class IdentityHashMap<K,V>

   其中,K指定了键的类型,V指定了值的类型。API文档明确指出IdentityHashMap不用于通用目的。
   5.EnumHashMap类
   EnumHashMap扩展了AbstractMap类并实现了Map接口,是专门为了使用枚举类型的键而设计的。EnumMap是泛型类,其声明以下:

class EnumMap<K extends Enum<K>,V>

   其中,K指定了键的类型,V指定了值的类型。注意K必须扩展Enum<K>,从而强制满徐键必须是枚举类型这一需求。
   EnumMap定义了如下构造函数:

EnumMap(Class<K> kType)
EnumMap(Map<K,? extends V> m)
EnumMap(EnumMap<K,? extends V> em)

   第1种形式建立一个类型为kType的空EnumMap。第2种形式建立一个包含m中相同条目的EnumMap。第3种形式建立一个使用em中的值进行初始化的EnumMap。
   EnumMap类没有定义本身的方法。

10 比较器

   TreeSet和TreeMap类以有序顺序存储元素。然而,是比较器精肯定义了“有序顺序”的含义。默认状况下,这些类使用Java称为“天然顺序”的方式对元素进行排序,天然顺序一般是咱们所指望的顺序(A在B以前,1在2以前,等等)。若是但愿以不一样的方式排序元素,能够在构造组或映射时指定比较器,这样就能够精确控制在有序集合和映射中存储元素的方式。
   Comparator是泛型接口,其声明以下:

interface Comparator<T>

   其中,T指定了将要进行比较的对象的类型。
   在JDK8以前,Comparator接口只定义了两个方法:compare()和equals()。compare()方法以下所示,用于比较两个元素以进行排序:

int compare(T obj1,T obj2)

   obj1和obj2是要进行比较的对象。正常状况下,若是对象相等,该方法返回0;若是obj1大于obj2,返回一个正直,反之返回一个负值。若是要进行比较的对象的类型不兼容,该方法会抛出ClassCastException异常。经过实现compare()方法,能够改变对象的排序方式。例如,为了按照相反的顺序进行排序,能够建立比较器以翻转比较结果。
   equals()方法以下所示,用于测试某个对象是否等于比较器:

boolean equals(Object obj)

   其中,obj是将要进行相等测试的对象。若是obj和调用对象都是比较器,而且使用相同的排序规则,那么该方法返回true;不然返回false。没必要重写equals()方法,而且大多数简单的比较器都不重写该方法。
   多年来,上述两个方法是仅有的Comparator定义的方法,然而JDK8改变了这种状况。经过使用默认接口方法和静态方法,JDK8为Comparator添加了许多新功能。下面逐一进行介绍:

default Comparator<T> reversed()

   该方法返回颠倒后的比较器。例如,假设一个比较器为字母A~Z使用天然顺序,在颠倒其顺序后的比较器中,B将出如今A的前面,C将出如今B的前面,依此类推。
   reverseOrder()是reverse()关联的一个方法,以下所示:

static <T extends Comparable<? super T>> Comparator<T> naturalOrder()

   若是但愿比较器可以处理null值,须要使用下面的nullsFirst()和nullsLast()方法。

static <T> Comparator<T> nullsFirst(Comparator<? super T> comp)
static <T> Comparator<T> nullsLast(Comparator<? super T> comp)

   nullsFirst()方法返回的比较器认为null比其余值小,nullsLast()方法返回的比较器认为null比其余值大。对于这两个方法,若是被比较的两个值都是非
null值,则comp执行比较。若是comp传递null,则认为全部非null值都是相等的。
   JDK8添加的另一个默认方法是thenComparing()。该方法返回一个比较器,当第一次比较的结果指出被比较的对象相等是,返回的这个比较器将执行第二次比较。所以,可使用该方法建立一个"根据X比较,而后根据Y比较"的序列。例如,当比较城市时,第一次可能比较城市名,第二次则比较州名。所以,若是使用字母顺序,Illiois州的SpringField将出现Missouri州的SpringField的前面。thenComparing()方法有三种形式。第一种形式以下所示:

default Comparator<T> thenComparing(Comparator<? super T> themByComp)

   其中,themByComp指定在第一次比较返回相等后调用的比较器。
   thenByComparing()的另外两个版本容许指定标准函数式接口Function(由java.util.function定义),以下所示:

default <U extends Comparable<? super U> Comparator<T> thenComparing(Function<? super T,? extends U> getKey)
default <U> Comparator<T> thenComparing(Function<? super T,? extends U> getKey,Comparator<? super U> keyComp)

   在这两个版本中,getKey引用的函数用于得到下一个比较简键,当第一次比较返回相等后,将使用这个比较键。后一个版本中keyComp指定了用于比较键的比较器(在这里之后后面的使用中,U指定了键的类型)。
   Comparator还为基本类型添加了如下专用版本的“then comparing”方法:

default Comparator<T> themComparingDouble(ToDoubleFunction<? super T> getKey)
default Comparator<T> thenComparingInt(ToIntFunction<? super T> getKey)
default Comparator<T> thenComparingLong(ToLongFunction<? super T> getKey)

   在全部这些方法中,getKey引用的函数用于获取下一个比较键。
   最后,JDK8在Comparator中添加了comparing()方法。该方法返回的比较器从传递该方法的函数中得到比较键。comparing()方法有两个版本,以下所示:

static <T,U extends Comparable<? super U>> Comparator<T> comparing(Function<? super T,? extends U> getKey)
static <T,U> Comparator<T> comparing(Function<? super T,? extends U> getKey,Comparator<? super U> keyComp)

   在这两个版本中,getKey引用的函数用于得到下一个比较键。在第二个版本中,keyComp指定了用于比较键的比较器。Comparator还为基本类型添加了以下所示的专用版本的方法:

static <T> Comparator<T> ComparingDouble(ToDoubleFunction<? super T> getKey)
static <T> Comparator<T> ComparingInt(ToIntFunction<? super T> getKey)
static <T> Comparator<T> ComparingLong(ToLongFunction<? super T> getKey)

   在全部这些方法中,getKey引用的函数用于得到下一个比较键。
使用比较器
   下面是一个演示自定义比较器功能的例子。该例为字符串实现了compare()方法,以与正常顺序相反的顺序进行操做。所以,这将致使树组(tree set)以相反的顺序进行存储。

//Use a custom comparator.
import java.util.Comparator;
//A reverse comparator for strings.
class MyComp implements Comparator<String> {
    @Override
    public int compare(String aStr, String bStr) {
        //Reverse the comparison
        return bStr.compareTo(aStr);
    }
    //No need to override equals or the default methods.
}
import java.util.TreeSet;
class CompDemo {
  public static void main(String[] args) {
       //Create a tree set.
       TreeSet<String> ts = new TreeSet<String>(new MyComp());
       //Add elements to the tree set.
       ts.add("C");
       ts.add("A");
       ts.add("B");
       ts.add("E");
       ts.add("F");
       ts.add("D");
       //Display the elements
       for (String element:ts){
           System.out.print(element+" ");
       }
       System.out.println();
       /** * 输出结果: * F E D C B A * 树如今以相反的顺序进行存储 */
   }
}

   下面详细分析MyComp类,该类经过实现compare()方法实现了Comparator接口(如前所示,重写equals()方法既不是必须得,也不常见。对于JDK8新增的默认方法,也不是必须重写它们)。在compare()方法内部,使用String的compareTo()方法比较两个字符串。然而,是使用bStr——而不是aStr——调用compareTo()方法,这致使比较的输出结果被翻转。
   尽管上面的程序实现逆序比较器的方法彻底能够接受,可是从JDK8开始,还有另一种方法能够得到解决方案。即,如今能够简单地对天然顺序比较器调用reversed()方法。该方法返回一个等价的比较器,不过比较器的顺序是相反的,在前面的程序中,能够把MyComp重写为天然顺序比较器,以下所示:

class MyComp implements Comparator<String> {
    @Override
    public int compare(String aStr, String bStr) {
        return aStr.compareTo(bStr);
    }
}

   而后,可使用下面的代码建立一个反向排序字符串元素的TreeSet:

MyComp mc = new MyComp();//Create a comparator
//Pass a reverse order version of MyComp to TreeSet.
TreeSet<String>ts = new TreeSet<String>(mc.reversed());

   若是把这段代码放到前面的程序中,获得的几个与原来是同样的。在这个例子中,使用reversed()方法可以方便地得到逆序比较器,而不须要显式对其编码。
   从JDK8开始,在前面的例子中,实际上没有必要建立MyComp类,由于能够很方便地使用lambda表达式做为替代。例如,能够完全删除MyComp类,使用下面的代码建立字符串比较器:

//Use a lambda expression to implement Comparator<String>.
Comparator<String> mc = (aStr,bStr)->aStr.compareTo(bStr)

   另外还有一点:在这个简单的示例中,经过使用lambda表达式,能够在对TreeSet()构造函数的调用中直接指定逆序比较器,以下所示:

//Pass a reversed comparator to TreeSet() via a lambda expression.
TreeSet<String> ts = new TreeSet<String>((aStr,bStr)->bStr.compareTo(aStr));

   作了上述修改后,程序得以显著缩短。下面显示了最终的版本:

import java.util.TreeSet;
//Use a lambda expression to create a reverse comparator.
class CompDemo2 {
  public static void main(String[] args) {
       //Pass a reverse comparator to TreeSet() via a lambda expression.
       TreeSet<String> ts = new TreeSet<String>((aStr, bStr) -> bStr.compareTo(aStr));
       //Add elements to the tree set.
       ts.add("C");
       ts.add("A");
       ts.add("B");
       ts.add("E");
       ts.add("F");
       ts.add("D");
       //Display the elements.
       for (String element : ts) {
           System.out.print(element + " ");
       }
       /** * 输出: * F E D C B A */
   }
}

   做为使用自定义比较器的更实际的例子,下面的程序是前面显示的存储帐户金额的TreeSet程序的升级版。在前面的版本中,帐户根据名字进行存储,但排序是从名开始的。下面的程序根据姓对帐户进行排序。为此,使用比较器比较每一个帐户的姓,这会使得映射根据姓进行排序。

//Use a comparator to sort accounts by last name.
import java.util.Comparator;
//Compare last whole words in two strings.
class TComp implements Comparator<String> {
  @Override
   public int compare(String aStr, String bStr) {
       int i,j,k;
       //Find index of beginning of last name.
       i = aStr.lastIndexOf(' ');
       j = bStr.lastIndexOf(' ');
       k = aStr.substring(i).compareToIgnoreCase(bStr.substring(j));
       if(k==0){
           //last name match,check entire name
           return aStr.compareToIgnoreCase(bStr);
       }else{
           return k;
       }
       //No need to override equals.
   }
}
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
class TreeMapDemo2 {
 public static void main(String[] args) {
      //Create a tree map.
      TreeMap<String, Double> tm = new TreeMap<String, Double>(new TComp());
      //Put elements to the map.
      tm.put("John Doe", new Double(3434.34));
      tm.put("Tom Smith", new Double(123.22));
      tm.put("Jane Baker", new Double(1378.00));
      tm.put("Tod Hall", new Double(99.22));
      tm.put("Ralph Smith", new Double(-19.08));
      //Get a set of the entries.
      Set<Map.Entry<String, Double>> set = tm.entrySet();
      //Display the elements.
      for (Map.Entry<String, Double> me : set) {
          System.out.print(me.getKey() + ": ");
          System.out.println(me.getValue());
      }
      System.out.println();
      //Deposit 1000 into Jhon Doe's account.
      double balance = tm.get("John Doe");
      tm.put("John Doe", balance + 1000);
      System.out.println("John Doe's new balance: " + tm.get("John Doe"));
      /** * 输出,如今帐户根据姓进行排序 * Jane Baker: 1378.0 * John Doe: 3434.34 * Tod Hall: 99.22 * Ralph Smith: -19.08 * Tom Smith: 123.22 * * John Doe's new balance: 4434.34 */
  }
}

   比较器类TComp比较包含名和姓的两个字符串。该类首先比较姓。为此,查找每一个字符串最后一个空格的索引,而后比较每一个元素从该索引位置开始字符串。若是姓相同,就比较名。这使得树映射根据姓进行排序,而且若是姓相同的话,再根据名进行排序。从输出中能够看到这一点,由于Ralph Smith在Tom Smith以前。
   若是使用的是JDK8或更高的版本,还有另一种方法来编码前面的程序,让映射首先根据姓进行排序,而后根据名进行排序。这会用到thenComparing()方法。thenComparing()容许指定第二个比较器,当调用比较器返回相等时,就会使用第二个比较器。下面的程序演示了这种方法,它修改了前面的程序,使之使用thenComparing()方法:

//Use thenComparing() to sort by last,then first name.
import java.util.Comparator;
//A comparator that compares last names.
class CompLastNames implements Comparator<String> {
    @Override
    public int compare(String aStr, String bStr) {
        int i,j;
        //Find index of beginning of last name.
        i = aStr.lastIndexOf(' ');
        j = bStr.lastIndexOf(' ');
        return aStr.substring(i).compareToIgnoreCase(bStr.substring(j));
    }
}
import java.util.Comparator;
//Sort by entire name when last names are equals.
class CompThenByFirstName implements Comparator<String> {
    @Override
    public int compare(String aStr, String bStr) {
       return aStr.compareToIgnoreCase(bStr);
    }
}
import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
class TreeMapDemo2A {
public static void main(String[] args) {
      //Use thenComparing() to create a comparator that compares
      //last names,then compares entire name when last names match.
      CompLastNames compLN = new CompLastNames();
      Comparator<String> compLastThenFirst = compLN.thenComparing(new CompThenByFirstName());
      //Create a tree map.
      TreeMap<String, Double> tm = new TreeMap<String, Double>(compLastThenFirst);
      //Put elements to the map.
      tm.put("John Doe", new Double(3434.34));
      tm.put("Tom Smith", new Double(123.22));
      tm.put("Jane Baker", new Double(1378.00));
      tm.put("Tod Hall", new Double(99.22));
      tm.put("Ralph Smith", new Double(-19.08));
      //Get a set of the entires.
      Set<Map.Entry<String, Double>> set = tm.entrySet();
      //Display the elements.
      for (Map.Entry<String, Double> me : set) {
          System.out.print(me.getKey() + ": ");
          System.out.println(me.getValue());
      }
      System.out.println();
      //Reposit 1000 into John Doe's account.
      double balance = tm.get("John Doe");
      tm.put("John Doe",balance+1000);
      System.out.println("John Doe's new balance: "+tm.get("John Doe"));
      /** * 输出: * Jane Baker: 1378.0 * John Doe: 3434.34 * Tod Hall: 99.22 * Ralph Smith: -19.08 * Tom Smith: 123.22 * * John Doe's new balance: 4434.34 */
  }
}

   这个版本产生的输入与原来的版本相同,两者惟一的区别在于完成工做的方式。首先,注意这里建立了一个叫作CompLastNames的比较器,它只比较姓。第二个比较器CompThenByFirstName比较,名。接下来,使用下面的代码建立了TreeMap:

CompLastNames compLN = new CompLastNames();
Comparator<String> compLastThenFirst = compLN.themComparing(new CompThenByFirstName());

   这里的主比较器是compLN,它是CompLastNames的一个实例。对该实例调用thenComparing()方法,并传入CompThenByFirstName的一个实例。结果被赋值给名为compLastThenfFrst的比较器。这个比较器用于构造TreeMap,以下所示:

TreeMap<String,Double> tm = new TreeMap<String,Double>(compLastThenFirst);

   如今,每当比较的姓相同时,就会比较名,以便进行排序。这意味着首先根据姓对姓名进行排序,若是姓相同的话,再根据名进行排序。
   最后,本例显示建立了两个比较器类CompLastNames和ComThenByFirstName,可是其实可使用lambda表达式。

import java.util.Comparator;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
class TreeMapDemo2A2 {
  public static void main(String[] args) {
      Comparator<String> comp1 = (String aStr,String bStr)-> aStr.substring(aStr.lastIndexOf(' ')).compareToIgnoreCase(bStr.substring(bStr.lastIndexOf(' ')));
      Comparator<String> comp2=comp1.thenComparing((aStr,bStr)->aStr.compareToIgnoreCase(bStr));
      //Create a tree map.
      TreeMap<String, Double> tm = new TreeMap<String, Double>(comp2);
      //Put elements to the map.
      tm.put("John Doe", new Double(3434.34));
      tm.put("Tom Smith", new Double(123.22));
      tm.put("Jane Baker", new Double(1378.00));
      tm.put("Tod Hall", new Double(99.22));
      tm.put("Ralph Smith", new Double(-19.08));
      //Get a set of the entires.
      Set<Map.Entry<String, Double>> set = tm.entrySet();
      //Display the elements.
      for (Map.Entry<String, Double> me : set) {
          System.out.print(me.getKey() + ": ");
          System.out.println(me.getValue());
      }
      System.out.println();
      //Reposit 1000 into John Doe's account.
      double balance = tm.get("John Doe");
      tm.put("John Doe",balance+1000);
      System.out.println("John Doe's new balance: "+tm.get("John Doe"));
      /** * 输出: * Jane Baker: 1378.0 * John Doe: 3434.34 * Tod Hall: 99.22 * Ralph Smith: -19.08 * Tom Smith: 123.22 * * John Doe's new balance: 4434.34 */
  }
}

11 集合算法

   集合框架定义了一些能够应用于集合和映射的集合算法,这些算法被定义为Collections类中的静态方法,表19汇总了这些方法。如前所述,从JDK5开始,Java对全部这些方法都进行了重写以使用泛型。

表19 Collections类定义的方法
方 法 描 述
static<T> boolean addAll(Collection<? super T> c,T… elements) 将由elements指定的元素插入由c指定的集合中。若是元素被插入c中,就返回true;不然返回false
static<T> Queue<T> asLifoQueue(Deque<T> c) 返回c的一个后进先出的视图
static<T> int binarySearch(List<? extends T> list,T value,Comparator<? super T> c) 根据c以肯定的顺序在list中查找value。返回value在list中为位置,若是没有找到,就返回一个负值
static<T> int binarySearch(List<? extends Comparable<? super T>>list,T value) 在list中查找value,列表必须是排过序的。返回value在list中的位置,若是没有找到value,就返回一个负值
static<E> List<E> checkedMap(Map<K,V> c,Class<K> keyT,Class<V> valueT) 返回Map的一个运行时类型安全视图。若是试图插入不兼容的元素,那么会致使ClassCastException异常
static<K,V> NavigableMap<K,V> checkedNavigableMap(NavigableMap<K,V> nm,Class<E> keyT,Class<V> valueT) 返回NavigableMap的一个运行时类型安全视图。若是试图插入不兼容的元素,那么会致使ClassCastException异常(JDK8新增)
static<E> Queue<E> checkedQueue(Queue<E> q,Class<E> t) 返回Queue的一个运行时类型安全视图。若是试图插入不兼容的元素,那么会致使ClassCastException异常(JDK8新增)
static<E> List<E> checkedSet(Set<E> c,Class<E> t) 返回Set的一个运行时类型安全视图。若是试图插入不兼容的元素。那么会致使ClassCastException异常
static<K,V> SortedMap<K,V> checkedSortedMap(SortedMap<K,V> c,Class<K> keyT,Class<V> valueT) 返回SortedMap的一个运行时类型安全视图。若是试图插入不兼容的元素,那么会致使ClassCastException异常
static<E> SortedSet<E> checkedSortedSet(SortedSet<E> c,Class<E> t) 返回SortedSet的一个运行时类型安全视图。若是试图插入不兼容的元素,那么会致使ClassCastException异常。
static<T> void copy(List<? super T> list1,List<? extends T> list2) 将list2中的元素复制到list1中
static boolen disjoint(Collection<?> a,Collection<?> b) 比较a和b中的元素,若是两个集合不包含公共元素,就返回true;不然返回false
static<E> Enumeration<T> emptyEnumeration() 返回一个空的枚举,即没有元素的迭代器
static<T> Iterator<T> emptyIterator() 返回一个空的迭代器,即没有元素的迭代器
static<T> List<T> emptyList() 返回一个推断类型的、不可变得空Map对象
static<T> ListIterator<T> emptyListIterator() 返回一个空的列表迭代器,即没有元素的列表迭代器
static<K,V> Map<K,V> emptyMap 返回一个推断类型的、不可变得空Map对象
static<K,V> NavigableMap<K,V> emptyNavigableMap() 返回一个推断类型的、不可变的空NavigableMap对象(JDK8新增)
static<E> NavigableSet<E> emptyNavigableSet() 返回一个推断类型的、不可变的空NavigableSet对象(JDK8新增)
static<T> Set<T> emptySet() 返回一个推断类型的、不可变的空Set对象
static<K,V> SortedMap<K,V> emptySortedMap() 返回一个推断类型的、不可变的空SortedMap对象(JDK8新增)
static<E> SortedSet<E> emptySortedSet() 返回一个推断类型的、不可变得空SortedSet对象(JDK8新增)
static<T> Enumeration<T> enumeration(Collection<T> c) 返回一个基于集合c的枚举
static<T> void fill(List<? super T> list,T obj) 将obj赋给list中的每一个元素
static int frequency(Collection<?> c,Object obj) 计算obj在集合c中出现的次数并返回结果
static int indexOfSubList(List<?> list,List<?> subList) 查找list中subList首次出现的位置。返回首次匹配时的索引位置。若是没有找到匹配,就返回-1
static int lastIndexOfSubList(List<?> list,List<?> subList) 查找list中subList最后一次出现的位置。返回最后匹配时的位置索引。若是没有找到匹配,就返回-1
static<T> ArrayList<T> list(Enumeration<T> enum) 返回一个包含enum中元素的ArrayList
static<T> T max(Collection<? extends T> c,Comparator<? super T> comp) 返回集合c中由comp决定的最大元素
static<T extends object & Comparable<? super T>> T max(Collection<? extends T> c) 返回几个c中由天然顺序决定的最大元素,集合没必要是排过序的
static<T> T min(Collection<? extends T> c,Comparator<? super T> comp) 返回集合c中由comp决定的最小元素,集合没必要是排过序的
static<T extends object & Comparable<? super T>> T min(Collection<? extends T> c) 返回集合c中天然顺序决定的最小元素
static<T> List<T> nCopies(int num,T obj) 返回包含在一个不可变列表中的obj的num个副本,num必须大于等于0
static<E> Set<E> newSetFromMap(Map<E,Boolean> m) 建立并返回由m指定的映射所支持的一个集合。调用该方法时,映射必须为空
static<T> boolean replaceAll<List<T> list,T old,T new> 将list中的全部old替换为new。若是至少发生了一次替换就返回true;不然返回false
static void reverse(List list) 颠倒list中元素的顺序
static<T> Comparator<T> reverseOrder(Comparator<T> comp) 返回一个逆序比较器,基于传递到comp中的比较器,也就是说,返回的比较器会调度comp比较的结果
static<T> Comparator<T> reversedOrder() 返回一个逆序比较器,也就是颠倒两个元素比较结果的比较器
static void rotate(List<T> list,int n) 将list向右旋转n个位置。要向左旋转,可以使用n的负值
static void shuffle(List<T> list,Random r) 使用r做为随机数的来源,搅乱(即随机化)list中的元素
static void shuffle(List<T> list) 搅乱(即随机化)list中的元素
static <T> Set<T> singleton(T obj) 将obj做为不可变得组返回,这是将单个对象转换成组的一种简单方式
static<T> List<T> singletonList(T obj) 将obj做为不可变的列表返回,这是将单个对象转换成列表的一种简单方式
static<K,V> Map<K,V> singletonMap(K k,V v) 将键/值对k/v做为不可变的映射返回,这是将单个键/值对转换成映射的一种简单方式
static<T> void sort(List<T> list,Comparator<? super T> comp) 按照由comp决定的顺序对list中的元素进行排序
static <T extends Comparable<? super T>> void sort(List<T> list) 按照天然顺序对list中的元素进行排序
static void swap(List<?> list,int idx1,int idx2) 交换list中由idx1和idx2指定的索引位置的元素
static<T> Collection<T> synchronizedCollection(Collection<T> c) 返回基于集合c的线程安全的集合
static<T> List<T> synchronizedList(List<T> list) 返回基于list的线程安全的列表
static<K,V> Map<K,V> synchoronizedMap(Map<K,V> m) 返回基于m的线程安全的映射
static<K,V> NavigableMap<K,V> synchronizedNavigableMap(NavigableMap<K,V> nm) 返回基于nm的同步的NavigableMap对象(JDK8新增)
static<T> NavigableSet<T> synchronizedNavigableSet(Navigable<T> ns) 返回基于ns的同步NavigableSet对象(JDK8新增)
static<T> Set<T> synchronizedSet(Set<T> s) 返回基于s的线程安全的组
static<K,V> SortedMap<K,V> synchronizedSortedSet(SortedSet<T> ss) 返回基于ss的线程安全的有序组
static<T> Collection<T> unmodifiableCollection(Collection<? extends T> c) 返回基于c的不可修改的列表
static<K,V> Map<K,V> unmodifiableMap(Map<? extends K,? extends V> m) 返回基于m的不可修改的映射
static<K,V> NavigableMap<K,V> unmodifiableNavigableMap(NavigableMap<K,? extends V nm>) 返回基于nm的不可修改的NavigableMap对象(JDK8新增)
static<T> NavigableSet<T> unmodifiableNavigableSet(NavigableSet<T> ns) 返回基于ns的不可修改的NavigableSet对象
static<T> Set<T> unmodifiableSet(Set<? extends T> s) 返回基于s的不可修改的组
static<K,V> SortedMap<K,V> unmodifiableSortedMap(SortedMap<K,?extends V> sm) 返回基于sm的不可修改的有序映射
static<T> SortedSet<T> unmodifiableSortedSet(SortedSet<T> ss) 返回基于ss的不可修改的有序组

   当试图比较不兼容的类型时,有些方法会抛出ClassCastException异常;当试图修改不可修改的集合时,有些方法会抛出UnSupportedOperationException。根据具体的方法不一样,还可能抛出其余异常。
   须要特别注意的是checked方法系列,例如checkedCollection(),这类方法返回API文档中所说的集合的"动态类型安全视图"。这种视图是对集合的引用,用于在运行时监视向集合中进行插入操做的类型兼容性。若是试图插入不兼容的元素,会致使ClassCastException异常。在调试期间使用这种视图特别有用,由于能够确保集合老是包含有效的元素。相关方法包括checkedSet()、checkedList()、checkedMap()等。它们为指定的集合获取类型安全的视图。
   注意有些方法用于获取各类集合的同步(线程安全的)副本,例如synchronizedList()和synchronizedSet()。做为通常规则,标准集合实现不是同步的。必须使用同步算法提供同步。另外一点:同时集合的迭代器必须在synchronized代码块中使用。
   以unmodifiable开始的一组方法集返回各类不能修改的集合视图。若是但愿确保对集合进行某些读取操做——可是不容许写入操做,这些方法是有用的。
   Collections定义了3个静态变量:EMPTY_SET、EMPTY_LIST和EMPTY_MAP。全部这3个变量都是不可改变得。
   下面的程序演示了一些算法,建立并初始化了一个链表。reverseOrder()方法返回翻转Integer对象比较结果的比较器。列表元素根据这个比较器进行排序,而后显示。接下来,调用shuffle()方法以随机化链表,而后显示链表中的最小值和最大值。

//Demonstrate various algorithms
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedList;
class AlgorithmsDemo {
public static void main(String[] args) {
      //Create an initialize linked list.
      LinkedList<Integer> ll = new LinkedList<Integer>();
      ll.add(-8);
      ll.add(20);
      ll.add(-20);
      ll.add(8);
      //Create a reverse order comparator.
      Comparator<Integer> r = Collections.reverseOrder();
      //Sort list by using the comparator.
      Collections.sort(ll, r);
      System.out.print("List sorted in reverse: ");
      for (int i : ll) {
          System.out.print(i + " ");
      }
      System.out.println();
      //Shuffle list.
      Collections.shuffle(ll);
      //Display randomized list.
      System.out.print("List shuffle: ");
      for (int i : ll) {
          System.out.print(i + " ");
      }
      System.out.println();
      System.out.println("Minimum: " + Collections.min(ll));
      System.out.println("Maxinum: " + Collections.max(ll));
      /** * 输出: * List sorted in reverse: 20 8 -8 -20 * List shuffle: 20 -20 8 -8 * Minimum: -20 * Maxinum: 20 */
  }
}

   注意是在随机化以后,才对链表进行min()和max()操做的。这;两个方法的操做都不要求列表是排过序的。

12 Arrays类

   Arrays类提供了对数组操做有用的方法,这些方法有助于链接集合和数组。本节将解释Arrays类定义的每一个方法。
   asList()方法返回基于指定数组的列表。换句话说,列表和数组引用相同的位置。asList()方法的签名以下所示:

static <T> List asList(T... array)

   其中,array是包含数据的数组。
   binarySearch()方法使用二分搜索法查找特定数组。只能为排过序的数组应用这个方法,下面是这个方法的几种形式(附加形式容许查找子范围):

static int binarySearch(byte array[],byte value)
static int binarySearch(char array[],char value)
static int binarySearch(double array[],double value)
static int binarySearch(float array[],float value)
static int binarySearch(int array[],int value)
static int binarySearch(long array[],long value)
static int binarySearch(short array[],short value)
static int binarySearch(Object array[],Object value)
static <T> int binarySearch(T[] array,T value,Comparator<? super T> c)

   其中,array是将要进行搜索的数组,value是被定位的值。若是数组包含的元素不能进行比较(例如Double和StringBuffer),或者若是value和array中的类型不兼容,那么后面两种形式会抛出ClassCastException异常。在最后一种形式中,比较器c用于肯定array中元素的顺序。对于全部这些形式,若是value存在于array中,就返回元素的索引;不然返回一个负值。
   copyOf()方法返回数组的副本,具体有一下几种形式:

static boolean[] copyOf(boolean[] source,int len)
static byte[] copyOf(byte[] source,int len)
static char[] copyOf(char[] source,int len)
static double copyOf(double[] source,int len)
static float copyOf(float[] source,int len)
static int[] copyOf(int[] source,int len)
static long[] copyOf(long[] source,int len)
static short[] copyOf(short[] source,int len)
static <T> T[] copyOf(T[] source,int lem)
static<T,U> T[] copyOf(U[] source,int len,Class<? extends T[]> resultT)

   原始数组是由source指定的,副本数组的长度是由len指定的。若是副本数组的长度大于source,就是用0(对于数值数组)、null(对于对象数组)或false(对于布尔数组)填充。若是副本数组的长度小于
source,那么副本数组会被截断。对于最后一种形式,resultT的类型变成了返回的数组的类型。若是len是负值,那么抛出NegativeArraySizeExcpetion异常;若是source是null,那么抛出NullPointerException异常;若是resultT与source的类型不兼容,那么抛出ArrayStoreException异常。
   copyOfRange()方法返回数组中某个范围的副本,具备如下形式:

static boolean[] copyOfRange(boolean[] source,int start,int end)
static byte[] copyOfRange(byte[] source,int start,int end)
static char[] copyOfRange(char[] source,int start,int end)
static double[] copyOfRange(double[] source,int start,int end)
static float[] copyOfRange(float[] source,int start,int end)
static int[] copyOfRange(int[] source,int start,int end)
static long[] copyOfRange(long[] source,int start,int end)
static short[] copyOfRange(short[] source,int start,int end)
static <T> T[] copyOfRange(T[] source,int start,int end)
static <T,U> T[] copyOfRange(U[] source,int start,int end,Class<? extends T[]> resultT)

   原始数组是由source指定的。副本数组的范围是经过start和end传递的索引指定的。范围从start到end-1。若是长于source,那么副本数组使用0(对于数值数组)、null(对于对象数组)或false(对于布尔数组)填充。对于最后一种形式,resultT的类型变成了返回的数组的类型。若是start是负值或着大于source的长度,那么抛出ArrayIndexOutOfBoundsException异常;若是start大于end,那么抛出IllegalArgumentException异常;若是source是null,那么抛出NullPointerException异常;若是resultT与source的类型不兼容,那么抛出ArrayStoreException异常。
   若是两个数组相等,那么equals()方法返回true;不然返回false。equals()方法具备如下形式:

static boolean equals(boolean array1[],boolean array2[])
static boolean equals(byte array1[],byte array2[])
static boolean equals(char array1[],char array2[])
static boolean equals(double array1[],double array2[])
static boolean equals(float array1[],float array2[])
static boolean equals(int array1[],int array2[])
static boolean equals(long array1[],long array2[])
static boolean equals(short array1[],short array2[])
static boolean equals(Object array1[],Object array2[])

   其中,array1和array2是进行相等性比较的两个数组。
   deepEquals()方法用于比较两个可能包含嵌套数组的数组是否相等,其声明以下所示:

static boolean deepEquals(Object[] a,Object[] b)

   若是a和b中传递的数组包含相同的元素,该方法返回true;若是a和b包含嵌套的数组,那么还会检查这些嵌套数组的内容;若是数组或任何嵌套数组的内容不一样,那么返回false。
   fill()方法能够将某个值赋给数组中的全部元素。换句话说,使用指定的值填充数组。fill()方法有两个版本。第一个版本具备如下形式,该版本填充整个数组:

static void fill(boolean array[],boolean value)
static void fill(byte array[],boolean value)
static void fill(char array[],boolean value)
static void fill(double array[],boolean value)
static void fill(float array[],boolean value)
static void fill(int array[],boolean value)
static void fill(long array[],boolean value)
static void fill(short array[],boolean value)
static void fill(Object array[],Object value)

   在此,将value赋给array中的全部元素。fill()方法的第二个版本将值赋给数组的子集。
   sort()方法用于对数组进行排序,从而使其中的元素以升序进行排序。sort()方法有两个版本,第一个版本,以下所示,对整个数组进行排序:

static void sort(byte array[])
static void sort(char array[])
static void sort(double array[])
static void sort(float array[])
static void sort(int array[])
static void sort(long array[])
static void sort(short array[])
static void sort(Object array[])
static <T> void sort(T array[],Comparator<? super T> c)

   其中,array是进行排序的数组。对于最后一种形式,c是用于肯定array元素顺序的比较器。对于最后两种形式,若是进行排序的数组的元素不能惊醒比较,那么可能抛出ClassCastException异常。第二种版本的sort()方法容许指定但愿进行排序的数组的范围。
   JDK8为Arrays类添加了一些新方法,其中最重要的多是parallelSort()方法,由于该方法按升序对数组的各部分进行并行排序,而后合并结果,这种方法能够显著加快排序时间。相似于sort()方法,parallelSort()方法有两种版本,每种形式都被重载屡次。第一种版本排序整个数组,以下所示:

static void parallelSort(byte array[])
static void parallelSort(char array[])
static void parallelSort(double array[])
static void parallelSort(float array[])
static void parallelSort(int array[])
static void parallelSort(long array[])
static void parallelSort(short array[])
static <T extends Comparable<? super T>> void parallelSort(T array[])
static <T> void parallelSort(T array[],Comparator<? super T> c)

   这里,array是进行排序的数组。对于最后一种形式,c是用于肯定array元素顺序的比较器。对于最后两种形式,若是进行排序的数组不能进行比较,那么可能抛出ClassCastException异常。第二种版本的parallelSort()方法容许指定但愿进行排序的数组的范围。
   经过包含spliterator()方法,JDK8为Arrays类添加了对spliterator的支持。该方法有两种基本形式。第一种版本返回整个数组的spliterator,以下所示:

static Spliterator.ofDouble spliterator(double array[])
static Spliterator.ofInt spliterator(int array[])
static Spliterator.ofLong spliterator(long array[])
static <T> Spliterator spliterator(T array[])

   这里,array是spliterator将循环遍历的数组。第二种版本的spliterator()方法容许指定但愿进行迭代的数组的范围。
   从JDK8开始,经过包含stream()方法,Arrays类支持新的Stream接口。stream()方法有两种版本,下面显示了第一种版本:

static DoubleStream stream(double array[])
static IntStream stream(int array[])
static LongStream(long array[])
static <T> Stream stream(T array[])

   这里,array是流将引用的数组。第二种版本的stream()方法容许指定数组内的一个范围。
   除了刚才讨论的方法,JDK8还添加了另外3个新方法。其中两个彼此相关:setAll()和parallelSetAll()。这两个方法都为全部元素赋值,可是parallelSetAll()以并行方式工做。这两个方法的声明以下所示:

static void setAll(double array[],IntToDoubleFunction<? extends T> genVal)
static void parallelSetAll(double array[],IntToDoubleFunction<? extends T> genVal)

   这两个方法都有一些重载形式,用于处理int、long和泛型。
   最后,JDK8还为Arrays类添加了一个有意思的方法:parallelPrefix()。该方法对数组进行修改,是每一个元素都包含对其前面的全部元素应用某个操做的累积结果。例如,若是操做是乘法,那么在返回时,数组元素将包含原始值的累积乘积。parallelPrefix()有几种重载形式,下面是其中的一种:

static void parallelPrefix(double array[],DoubleBinaryOperator func)

   这里,array是被操做的数组,func指定应用的操做(DoubleBinaryOperator是java.util.function中定义的一个函数式接口)。还有另外几种版本,包括操做int、long和泛型的版本,以及容许指定但愿操做的数组范围的版本。
   Arrays类还为每种类型的数组提供了toString()和hashCode()方法。此外,还提供了deepToString()和deepHashCode()方法,这两个方法能够有效操做包含嵌套数组的数组。
   下面的程序演示了如何使用Arrays类的一些方法:

//Demonstrate Arrays
import java.util.Arrays;
class ArraysDemo {
  public static void main(String[] args) {
       //Allocate and initialize array.
       int array[] = new int[10];
       for (int i = 0; i < 10; i++) {
           array[i] = -3 * i;
       }
       //Display,sort and display the array.
       System.out.print("Original content: ");
       display(array);
       Arrays.sort(array);
       System.out.print("Sorted: ");
       display(array);
       //Fill and display the array.
       Arrays.fill(array,2,6,-1);
       System.out.print("After fill(): ");
       display(array);
       //Sort and display the array.
       Arrays.sort(array);
       System.out.print("After sorting again: ");
       display(array);
       //Binary search for -9
       System.out.print("The value -9 is at location ");
       int index = Arrays.binarySearch(array,-9);
       System.out.println(index);
       /** * 输出: Original content: 0 -3 -6 -9 -12 -15 -18 -21 -24 -27 Sorted: -27 -24 -21 -18 -15 -12 -9 -6 -3 0 After fill(): -27 -24 -1 -1 -1 -1 -9 -6 -3 0 After sorting again: -27 -24 -9 -6 -3 -1 -1 -1 -1 0 The value -9 is at location 2 */
   }

   static void display(int array[]) {
       for (int i : array) {
           System.out.print(i + " ");
       }
       System.out.println();
   }
}

13 遗留的类和接口

   早期版本的java.util包没有包含集合框架,而是定义了几个类和一个接口,用来提供存储对象的专业方法。当添加集合时(由J2SE1.2添加),对几个原始类进行了从新设计以支持集合接口。所以从技术上讲,它们是集合框架的组成部分。然而,当如今集合的功能与遗留类的功能相同时,一般会但愿使用更新的一些集合类。通常来讲,仍然支持遗留类是由于有些代码仍然须要它们。
   另一点:在上面介绍的全部现代集合类都不是同步的,可是全部遗留类都是同步的。在有些状况下,这一差异可能很重要。固然,经过使用Collections提供的算法,能够很容易地同步集合。
   java.util定义的遗留类以下所示:
   Dictionary    HashTable    Properties    Stack    Vector

13.1 Enumeration接口

   使用Enumeration接口定义的方法能够枚举(每次获取一个)对象集合中的元素。这个遗留接口已经被Iterator接口取代。尽管不反对使用,可是对于新代码,Enumeration接口被认为是过期的。然而,遗留类(例如Vector和Properties)定义的一些方法使用该接口,其余一些API类也使用该接口。由于该接口仍然使用,全部JDK5对它进行了重修以使用泛型。Enumeration接口的声明以下:

interface Enumeration<E>

   在此,E指定了将被枚举的元素的类型。
   Enumeration接口定义了如下两个方法:

boolean hasMoreElements()
E nextElement()

   当实现该接口时,若是仍然有更多的元素要提取,那么hasMoreElements()方法必须返回true;若是已经枚举了全部元素,那么返回false。nextElement()方法返回枚举中的下一个元素。也就是说,每次调用nextElement()方法都会获取枚举中的下一个元素。当枚举完全部的元素时,该方法会抛出NoSuchElementException异常。

13.2 Vector类

   Vector实现了动态数组,与ArrayList相似,可是有两个区别:Vector是同步的,而且包含许多遗留方法,这些方法与集合框架定义的方法具备相同的功能。随着集合的出现,对Vector进行了从新设计以扩展AbstractList类并实现List接口。随着JDK5的发布,对Vector进行了重修以使用泛型,并进行了从新设计以实现Iterable接口。这意味着Vector与集合彻底兼容,而且能够经过使用加强的for循环来迭代Vector的内容。
   Vector类的声明以下所示:

class Vector<E>

   其中,E指定了将要存储的元素的类型。
   下面是Vector类的构造函数:

Vector()
Vector(int size)
Vector(int size,int incr)
Vector(Collection<? extends E> c)

   第1种形式建立默认的向量,初始大小为10。第2中形式建立的向量,初始大小由size指定。第3种形式建立的向量,由size指定,而且增量由incr指定。增量指定了每次增长向量时分配的元素数量。第4种形式建立包含集合c中元素的向量。
   全部向量都有初始容量。在达到这个初始容量后,下一次试图在向量中存储对象时,向量会自动为对象分配空间,外加用以保存更多对象的额外空间。经过分配比所需空间更多的空间,向量减小了当增加时必须分配空间的次数。减小分配空间的次数很重要,由于分配空间就时间而言是很昂贵的。在每次从新分配空间时,分配的额外空间的数量是由建立向量时指定的增量决定了。若是没有指定增量,每次分配空间时都会是向量的大小翻倍。
   Vector类定义了如下受保护的数据成员:

int capacityIncrement;
int elementCount;
Object[] elementData;

   增量值保存在capacityIncrement中,向量中当前元素数量保存在elementCount中,保存向量的数组存储在elementData中。
   除了List定义的集合方法外,Vector还定义了一些遗留方法,表20对这些方法进行了汇总。

表20 Vector类定义的遗留方法
方 法 描 述
void addElement(E element) 将element指定的对象添加到向量中
int capacity() 返回向量的容量
Object clone() 返回调用向量的副本
boolean contains(Object element) 若是向量中包含element,就返回true;不然返回false
void copyInto(Object array[]) 将调用向量中包含的元素复制到由array指定的数组中
E elementAt(int index) 返回位于index位置的元素
Enumeration<E> elements() 返回向量中元素的一个枚举
void ensureCapacity(int size) 将向量的最小容量设置为size
E firstElement() 返回向量的第一个元素
int indexOf(Object element) 返回element首次出现的位置的索引。若是对象不在向量中,就返回-1
int indexOf(Object element,int start) 返回element在start位置及以后第一次出现的位置的索引。若是对象不在向量的这一部分中,就返回-1
void insertElementAt(E element,int index) 将元素element添加到向量中,插入位置由index指定
boolean isEmpty() 若是向量为空,就返回true;若是向量包含一个或多个元素,就返回false
E lastElement() 返回向量中的最后一个元素
int lastIndexOf(Object element) 返回element最后一次出现的位置的索引。若是对象不在向量中,就返回-1
int lastIndexOf(Object element,int start) 返回element在start位置以前最后一次出现的位置的索引。若是对象不在向量的这一部分中,就返回-1
void removeAllElements() 状况向量。执行该方法后,向量的大小为0
boolean removeElement(Object element) 从向量中移除element.若是向量中存在指定对象的对个实例,那么只移除第一个。若是移除成功,就返回true;若是没有找到对象,就返回false
void removeElementAt(int index) 移除位于index指定位置的元素
void selElementAt(E element,int index) 将index指定的位置设置为element
void setSize(int size) 将向量中元素的数量设置为size,若是新的长度小于原来的长度,元素将丢失;若是新的长度大于原来的长度,将添加null元素
int size() 返回当前向量中元素的个数
String toString() 返回向量的等价字符串
void trimToSize() 将向量的容量设置为与当前拥有的元素个数相同

   由于Vector类实现了List接口,因此能够像使用ArrayList实例那样使用向量,也可使用Vector类的遗留方法操做向量。例如,在实例化Vector后,能够调用addElement()方法来添加元素;为了获取特定位置的元素,能够调用elementAt()方法;为了获取向量中的第一个元素,可使用firstElement()方法;为了检索最后一个元素能够调用lastElement()或removeElementAt()方法。
   下面的程序使用向量存储各类数值类型的对象。该程序演示了Vector定义的几个遗留方法,另外还演示了Enumeration接口。

import java.util.Enumeration;
import java.util.Vector;
//Demonstrate various Vector operations.
class VectorDemo {
  public static void main(String[] args) {
      //Initial size is 3,increment is 2
      Vector<Integer> v = new Vector<Integer>(3,2);
      System.out.println("Initial size: "+v.size());
      System.out.println("Initial capacity: "+v.capacity());
      v.addElement(1);
      v.addElement(2);
      v.addElement(3);
      v.addElement(4);
      System.out.println("Capacity after our additions: "+v.capacity());
      v.addElement(5);
      System.out.println("Current capacity: "+v.capacity());
      v.addElement(6);
      v.addElement(7);
      System.out.println("Current capacity: "+v.capacity());
      v.addElement(9);
      v.addElement(10);
      System.out.println("Current capacity: "+v.capacity());
      v.addElement(11);
      v.addElement(12);
      System.out.println("First Element: "+v.firstElement());
      System.out.println("Last Element: "+v.lastElement());
      if(v.contains(3)){
          System.out.println("Vector contains 3.");
      }
      //Enumerate the elements in the vector
      Enumeration<Integer> vEnum = v.elements();
      System.out.println("\nElements in vector:");
      while(vEnum.hasMoreElements()){
          System.out.print(vEnum.nextElement()+" ");
      }
      /** * 输出结果: * Initial size: 0 * Initial capacity: 3 * Capacity after our additions: 5 * Current capacity: 5 * Current capacity: 7 * Current capacity: 9 * First Element: 1 * Last Element: 12 * Vector contains 3. * * Elements in vector: * 1 2 3 4 5 6 7 9 10 11 12 */
  }
}

   不是依赖枚举遍历对象(向上面程序那样),而是使用迭代器。例如,可使用下面基于迭代器的代码替换程序中的相应代码:

//Use an iterator to display contents.
Iterator<Integer> vItr = v.iterator();
System.out.println("\nElements in vector:");
while(vItr.hasNext()){
    System.out.print(vItr.next()+" ");
}

   还可使用for-each风格的for循环遍历Vector,如前面的代码的已下版本所示:

//Use an enhanced for loop to display contents.
System.out.println("\nElements in vector:")
for(int i:v){
  System.out.print(i+" ");
}

   由于对于新代码不推荐使用Enumeration接口,因此一般会使用迭代器或for-each风格的for循环枚举向量的内容。固然,仍然存在许多使用Enumeration接口的遗留代码。幸运的是,枚举和迭代的工做方式几乎相同。

13.3 Stack类

   Stack是Vector的子类,实现了标准的后进先出的堆栈。Stack只定义了默认构造函数,用于建立空的堆栈,随着JDK5的发布,Java对Stack类进行了整修以使用泛型,其声明以下所示:

class Stack<E>

   其中,E指定了在堆栈中存储的元素的类型。
   Stack类包含Vector类定义的全部方法,并添加了一些本身的方法,表21中显示了这些方法。

表21 Stack类定义方法
方 法 描 述
boolean empty() 若是堆栈为空,就返回true;若是堆栈包含元素,就返回false
E peek() 返回堆栈顶部的元素,可是不移除
E pop() 返回堆栈顶部的元素,并在这个过程当中移除
E push(E element) 将element压入堆栈,element也被返回
int search(Object element) 在堆栈中查找element。若是找到,返回这个元素到堆栈顶部的偏移值;不然返回-1

   为了将对象放入堆栈的顶部,能够调用push()方法;为了移除并返回顶部的元素,能够调用pop()方法;可使用peek()方法返回但不移除顶部的元素;调用pop()或peek()方法时若是调用堆栈为空,那么会抛出EmptyStackException异常;若是在堆栈中没有内容,empty()方法将返回true;search()方法肯定对象是否存在于堆栈中,并返回将对象压入堆栈顶部所须要的的弹出次数。下面的例子建立了一个堆栈,向其中压入一些Integer对象,而后将它们弹出:

//Demonstrate the Stack class.
import java.util.EmptyStackException;
import java.util.Stack;
class StackDemo {
 static void showpush(Stack<Integer> st, int a){
      st.push(a);
      System.out.println("Push("+a+")");
      System.out.println("stack: "+st);
  }
  static void showpop(Stack<Integer> st){
      System.out.print("pop -> ");
      Integer a = st.pop();
      System.out.println(a);
      System.out.println("stack: "+st);
  }

  public static void main(String[] args) {
      Stack<Integer> st = new Stack<Integer>();
      System.out.println("stack: "+st);
      showpush(st,42);
      showpush(st,66);
      showpush(st,99);
      showpop(st);
      showpop(st);
      showpop(st);
      try {
          showpop(st);
      }catch (EmptyStackException e){
          System.out.println("empty stack");
      }
      /** * 输出: * stack: [] * Push(42) * stack: [42] * Push(66) * stack: [42, 66] * Push(99) * stack: [42, 66, 99] * pop -> 99 * stack: [42, 66] * pop -> 66 * stack: [42] * pop -> 42 * stack: [] * pop -> empty stack */
  }
}

   注意针对EmptyStackException的异常处理程序的捕获方式,以便可以巧妙得处理堆栈下溢。
   另一点:尽管不反对Stack,但ArrayDeque是更好的选择。

13.4 Dictionary类

   Dictionary是表示键/值对存储库的抽象类,其操做与Map相似。给定键和值,就能够外Dictionary对象中存储值。一旦存储了值,就可使用相应的键来检索这个值。所以,与映射相似,能够将字典想象成键/对的列表。尽管目前不反对使用,可是Dictionary被贵类为过期的遗留类,由于它已经被Map彻底取代。可是Dictionary任然在使用,所以下面对其进行完整讨论。
   随着JDK5的出现,Dictionary被修改为泛型,其声明以下所示:

class Dictionary<K,V>

   其中,K指定了键的类型,V指定了值的类型。表22列出了Dictionary类定义的抽象方法。

表22 Dictionary类定义的抽象方法
方 法 描 述
Enumeration<V> elements() 返回字典所包含值的枚举
v get(Object key) 返回的对象与key关联的值。若是key不在字典中,就返回null对象
boolean isEmpty 若是字典为空,就返回true;若是至少包含一个键,就返回false
Enumeration<E> keys() 返回字典所包含键的枚举
V put(K key,V value) 将键及相应的值插入字典中。若是在字典中原来不存在key,就返回null;若是key已经在字典中,就返回以前与key关联的值
V remove(Object key) 移除key及相应的值。返回与key关联的值。若是key不在字典中,就返回null
int size() 返回字典中条目的数量

   要添加键和值,可使用put()方法。使用get()方法能够检索给定键的值。分别经过keys()和elements()方法,键和值均可以做为Enumeration返回。size()方法返回在字典中存储的键/值对的数量,若是字典为空,isEmpty()方法将返回true。可使用remove()方法移除键/值对。
   请记住:
   Dictionary类是过期的,应当实现Map接口来获取键/值存储功能。

13.5 Hashtable类

   Hashtable是原始java.util的一部分,是Dictionary的具体实现。可是,随着集合的出现,Java对Hashtable进行了从新设计,使其也实现了Map接口。所以,Hashtable被集成到集合框架中。Hashtable和HashMap相似,可是它是同步的。
   与HashMap相似,Hashtable在哈希表中存储键/值对。可是,键和值都不能是null。当使用Hashtable时,须要制定做为键的对象以及与键关联的值。而后对键进行散列,产生的哈希码做为索引,在哈希表中对应索引的位置存储值。
   JDK5将Hashtable修改成泛型,其声明以下所示:

class Hashtable<K,V>

   其中,K指定了键的类型,V指定了值的类型。
   哈希表只能存储重写了hashCode()和equals()方法的对象,这两个方法是由Object定义的。hashCode()方法必须计算并返回对象的哈希码。固然,equals()方法用来比较两个对象。幸运的是,许多内置的Java类已经实现了hashCode()方法。例如,HashTable的通用类型使用String对象做为键。String实现了hashCode()和equals()方法。
   Hashtable的构造函数以下所示:

Hashtable()
Hashtable(int size)
Hashtable(int size,float fillRatio)
Hashtable(Map<? extends K,? extends V> m)

   第1个版本是默认构造函数。第2个版本建立由size指定初始大小的哈希表(默认大小时11)。第3个版本建立的哈希表,初始大小由size指定而且填充率由fillRatio指定。填充率必须在0.0到1.0之间,填充率决定了哈希表在增大容量以前的充满程序。具体地说,就是当元素的数量大于哈希表的容量与填充率的乘积时,将扩展哈希表。若是没有指定填充率,就使用0.75。最后,第4个版本建立的哈希表,使用m中的元素进行初始化,使用默认载入因子0.75。
   除了Map接口定义的方法以外(HashTable如今实现了Map接口),HashTable还定义了在表23中列出的遗留方法。若是试图使用null键或值,有些方法会抛出NullPointerException异常。

表23 Hashtable类定义的遗留方法
方 法 描 述
void clear() 重置并清空哈希表
Object clone() 返回调用对象的一个副本
boolean contains(Object value) 若是哈希表中存在等于value的值,就返回true;若是没找到,就返回false
boolean containsKey(Object key) 若是哈希表中存在等于key的键,就返回true;若是没找到,就返回false
boolean constainsValue(Object value) 若是哈希表中存在等于value的值,就返回true;若是没找到,就返回false
Enumeration<V> elements() 返回哈希表中所包含值的一个枚举
V get(Object key) 返回的对象包含与key关联的值。若是key不在哈希表中,就返回null对象
boolean isEmpty() 若是哈希表为空,就返回true;若是至少包含一个键,就返回false
Enumeration<K> keys() 返回哈希表中所包含键的一个枚举
V put(K key,V value) 返回键和值插入哈希表中。若是以前在哈希表中不存在key,就返回null;若是在哈希表中已经存在key,就返回以前与key关联的值
void rehash() 增长哈希表的容量并从新散列全部键
V remove(Object key) 移除key以及与之关联的值,返回与key关联的值。若是key不在哈希表中,就返回null对象
int size() 返回哈希表中条目的数量
String toString() 返回哈希表的等价字符串

   下面的例子从新编写了前面显示的银行帐户程序,使用哈希表存储银行帐户的名称以及当前余额:

//Demonstrate a HashTable
import java.util.Enumeration;
import java.util.Hashtable;
class HTDemo {
 public static void main(String[] args) {
      Hashtable<String,Double> balance = new Hashtable<String,Double>();
      Enumeration<String> names;
      String str;
      double bal;
      balance.put("John Doe",3434.34);
      balance.put("Tom Smith",123.22);
      balance.put("Jane Baker",1378.00);
      balance.put("Tod Hall",99.22);
      balance.put("Ralph Smith",-19.08);
      //Show all balances in hashtable.
      names = balance.keys();
      while (names.hasMoreElements()){
          str = names.nextElement();
          System.out.println(str+": "+balance.get(str));
      }
      //Deposit 1,000 into John Doe's account.
      bal = balance.get("John Doe");
      balance.put("John Doe",bal+1000);
      System.out.println("John Doe's new balance: "+balance.get("John Doe"));
      /** * 输出: * Tod Hall: 99.22 * Ralph Smith: -19.08 * John Doe: 3434.34 * Jane Baker: 1378.0 * Tom Smith: 123.22 * John Doe's new balance: 4434.34 */
  }
}

   重要的一点:与映射类相似:Hashtable不直接支持迭代器。所以,前面的程序使用枚举显示余额。可是,能够获取哈希表的组视图,进而使用迭代器。为此,简单地使用由Map定义的组视图方法,例如entrySet()或keySet()。例如,能够获取键的组视图,并使用迭代器或加强的for循环对它们进行遍历。下面是该程序修改后的版本,该版本演示了这种技术:

//Use iterators with a Hashtable.
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Set;
class HDemo2 {
 public static void main(String[] args) {
      Hashtable<String,Double> balance = new Hashtable<String,Double>();
      String str;
      double bal;
      balance.put("John Doe",3434.34);
      balance.put("Tom Smith",123.22);
      balance.put("Jone Baker",1378.00);
      balance.put("Tod Hall",99.22);
      balance.put("Ralph Smith",-19.08);
      //Show all balance in hsahtable.
      //First,get a set view of the keys.
      Set<String> set = balance.keySet();
      //Get an iterator.
      Iterator<String> itr = set.iterator();
      while(itr.hasNext()){
          str = itr.next();
          System.out.println(str+": "+balance.get(str));
      }
      //Deposit 1,000 into John Doe's account.
      bal = balance.get("John Doe");
      balance.put("John Doe",bal+1000);
      System.out.println("John Doe's new balance: "+balance.get("John Doe"));
  }
}
13.6 Properties类

   Properties是Hashtable的子类,用于保存值的列表。在列表中,键是String类型,值也是String类型。许多其余Java类都在使用Properties类。例如,当获取环境值时,System.getProperties()方法返回的对象的类型就是Properties。尽管Properties类自己不是泛型类,但其中的一些方法是泛型方法。
   Properties定义了如下实例变量:

Properties defaults;

   该变量包含与Properties对象关联的默认属性列表。Properties定义了如下构造函数:

Properties()
Properties(Properties propDefault)

   第1个版本建立没有默认值的Properties对象。第2个版本建立使用propDefault做为默认值的Properties对象。对于这两个版本,属性列表都是空的。
   除了继承自Hashtable的方法外,Properties类还定义了表24中列出的方法。此外,Properties还包含不同意使用的方法save(),该方法已经被store()取代,由于save()方法没法正确地处理错误。

表24 Properties类定义的方法
方 法 描 述
String getProperty(String key) 返回与key关联的值。若是key既不在列表中,也不在默认属性列表中,就返回null对象
String getProperty(String key,String defaultProperty) 返回与key管理的值。若是key既不在列表中,也不在默认属性列表中,就返回defaultProperty
void list(PrintStream streamOut) 将属性列表发送到与streamOut 连接的输出流
void list(PrintWriter streamOut) 将属性列表发送到streamOut连接的输出流
void load(InputStream streamIn) throws IOException 从与streamIn连接的输入六中输入一个属性列表
void load(Reader streamIn) throws Exception 从与streamIn连接的输入流中输入一个属性列表
void loadFromXML(InputStream streamIn) throws IOException,InvalidPropertiesFormatException 从与streamIn链接的XML文档输入一个属性列表
Enumeration<?> propertyNames() 返回键的一个枚举,包括在默认属性中能找到的那些键
Object setProperty(String key,String value) 将value与key相关联。返回以前与key关联的值;若是不存在这样的关联,就返回null
void store(OutputStream streamOut,String description) throws IOException 在写入description指定的字符串以后,属性列表被写的与streamOut连接的输出流中
void store(Writer streamOut,String description) 在写入description指定的字符串以后,属性列表被写到与streamOut连接的输出流中
void storeToXML(OutputStream streamOut,String description) throws IOException 在写入description指定的字符串以后,属性列表被写到与streamOut连接的XML文档中
void storeToXML(OutputStream streamOut,String description,String enc) 使用指定的字符编码,将属性列表以及由description指定的字符串写入与streamOut连接的XML文档中
Set<String> stringPropertyNames() 返回一组键

   Properties类有一个有用的特性,就是能够指定默认属性,若是没有值与特性的键关联,就会返回默认属性。例如在getProperty()方法中,能够在指定键时指定默认值,例如getProperty(“name”,“default value”)。若是没有找到“name”值,就返回“default value”,当构造Properties对象时,能够传递另一个Properties实例,用做新实例的默认属性。对于这种状况,若是对给定的Properties对象调用getProperty(“foo”),而且"foo"不存在,那么Java会在默认的Properties对象中查找"foo",这使得能够任意嵌套默认属性。
   下面的例子演示了Properties类的使用。建立一个属性列表,在该列表中,键是州的名称,值时首府城市的名称。注意在该程序中,查找弗罗里达
州首府城市的代码包含一个默认值。

//Demonstrate a Property list.
import java.util.Properties;
import java.util.Set;
class PropDemo {
public static void main(String[] args) {
      Properties defList = new Properties();
      defList.put("Florida","Tallahassee");
      defList.put("Wisconsin","Madison");
      Properties capitals = new Properties(defList);
      capitals.put("Illinois","Springfield");
      capitals.put("Missouri","Jefferson City");
      capitals.put("Washington","Olympia");
      capitals.put("California","Sacramento");
      capitals.put("Indiana","Indianapolis");
      //Get a set-view of the keys.
      Set<?> states = capitals.keySet();
      //Show all of the states and capitals
      for (Object name:states){
          System.out.println("The capital of "+name+" is "+capitals.getProperty((String)name)+".");
      }
      //Florida will now be found in the default list.
      String str = capitals.getProperty("Florida");
      System.out.println("The capital of Florida is "+str+".");
      /** * 输出: * The capital of Missouri is Jefferson City. * The capital of Illinois is Springfield. * The capital of Indiana is Indianapolis. * The capital of California is Sacramento. * The capital of Washington is Olympia. * The capital of Florida is Tallahassee. */
  }
}
13.7 使用store()和load()

   Properties类最有用的方面之一是:在Properties对象中保存的信息,可使用stroe()和load()方法很容易地存储到磁盘中以及从磁盘加载。在任什么时候候,均可以将Properties对象写入到流中或从流中读取。这使得对于实现简单的数据库来讲,属性列表特别方便。例如,下面的程序使用属性列表建立一个简单的计算机化的电话簿,用于存储姓名和电话号码。为了查找某我的的电话号码,输入他或她的姓名。该程序使用store()与load()方法存储和检索列表。当程序执行时,首先尝试从phonebook.dat文件加载列表。若是这个文件从中,就加载列表。而后能够向列表中添加内容。若是成功添加内容,那么当退出程序时会保存新的列表。注意,实现一个小的,可是功能完备、计算机的电话簿所须要的代码如此之少!

/*A simple telephone database that uses a property list. */
import java.io.*;
import java.util.Properties;
class PhoneBook {
 public static void main(String[] args) throws IOException {
      Properties ht = new Properties();
      BufferedReader br = new BufferedReader(new InputStreamReader((System.in)));
      String name,number;
      FileInputStream fin = null;
      boolean changed = false;
      //Try to open phonebook.dat file.
      try {
          fin = new FileInputStream("phonebook.dat");
      }catch (FileNotFoundException e){
          //ignore missing file
      }
      /*If phonebook file already exists, load existing telephone numbers. */
      try {
          if(fin != null){
              ht.load(fin);
              fin.close();
          }
      }catch (IOException e){
          System.out.println("Error reading file.");
      }
    //Let user enter new names and numbers.
      do{
          System.out.println("Enter new name"+" ('quit' to stop)");
          name = br.readLine();
          if(name.equals("quit")){
              continue;
          }
          System.out.println("Enter number: ");
          number = br.readLine();
          ht.put(name,number);
          changed= true;
      }while(!name.equals("quit"));
      //If phone book data has changed,save it.
      if(changed){
          FileOutputStream fout = new FileOutputStream("phonebook.dat");
          ht.store(fout,"Telephone Book");
          fout.close();
      }
      //Look up numbers given a name.
      do{
          System.out.println("Enter name to find"+" ('quit' to quit): ");
          name = br.readLine();
          if(name.equals("quit")){
              continue;
          }
          number = (String)ht.get(name);
          System.out.println(number);
      }while(!name.equals("quit"));
  }
}

14 小结

   集合框架针对最通用的编程任务,提供了一套功能强大、设计良好的解决方案。当须要存储和检索信息时,可考虑使用集合。请记住,集合不是专门针对“大任务”二设计的,例如企业数据库、邮件列表或库存系统。当应用于更小的任务时,它们也颇有效。例如,TreeMap能够建立一个优秀的集合,用于容纳一组文件的目录结构。对于存储项目管理信息而言,TreeSet可能至关有用。总之,从集合中受益的问题种类可能会超出咱们的想象。