集合框架

                                                         **集合框架**
                   **概述**
Java SE包含了由一组类和接口组成的Java集合框架(Java Collections Framework,简称JCF),其主要功能是用来将存储的数据以某种结构组织,并以特定的方式来访问这些数据,其目标是提供一个处理对象集合的通用框架,减少程序员处理不同对象集合时的编码量。
Java集合类都位于java.util包中,下图是JCF的类图。

在这里插入图片描述

在深入探讨组成Java集合框架的接口和类之前,我们首先来了解一下几个需要理解的概念和术语。
被添加到集合中的对象称为一个元素。有些集合类允许有重复的元素,有些则不允许。那么什么元素算是重复的呢?当两个元素通过使用equals()方法进行比较时,如果返回true值,那么这两个元素就是重复的。
集合类中的一些区别,除了它们是否支持重复元素以外,还包括元素是否有顺序,以及是否允许添加null元素。Java集合框架中根据这三个区别,将对象的存储方式分为三种类型,分别是:
    Set(集):对象容器中的对象没有顺序,且不能重复。
    List(列表):对象容器中的对象按照索引顺序排序,而且可以有重复的对象。
    Map(映射):对象容器中的元素包含一对"键对象-值对象"映射,其中键对象不能重复,值对象可以重复。
这三种对象的存储方式,对应了Java集合框架中的三个核心接口Set、List和Map。
在Set和List接口之上,定义接口Collection,用于定义存取Set和List类型容器中对象的一些通用操作,包括对象的增加、删除、遍历等。
为支持对象的排序和遍历访问操作,Java集合框架中又提供了几个接口:
    接口SortedSet为Set类型容器提供排序功能。
    接口SortedMap是为Map类型容器提供对键对象的排序。
    接口Iterator提供了对集合对象进行遍历访问的遍历器。
    接口Comparable和Comparator用来实现集合中对象的排序。
此外,为方便集合和数组的操作,Java集合框架中还提供了Collections和Arrays这两个功能强大的工具类,封装了一些复杂的数据结构算法等操作。

        **Collection接口和Iterator接口**
在集合框架中,集合(Collection)接口位于Set接口和List接口的最顶层,是Set接口和List接口的父接口。Collection接口中定义了Collection对象共有的一些基本方法,这些方法分为基本操作、批量操作和数组操作。基本操作是针对单个元素的操作,批量操作是同时对一批元素进行操作,数组操作是将集合转化为数组的操作。通过在Collection接口中定义这些共用的方法,可以方便程序员用统一的方法来存取对象,从而简化了程序员编写对象容器时的编码,提高了开发效率。

在这里插入图片描述

在这里插入图片描述

Iterator接口是一种用于遍历集合的接口。所谓遍历,是指从集合中取出每一个元素的过程。在Collection接口中,有一个iterator()方法,通过该方法可以返回一个Iterator对象。通过这个对象,可以遍历集合中所有元素。

在这里插入图片描述

虽然每个集合类都提供专用的机制来访问各自的数据,但它们都支持iterator方法,这就允许我们采用统一的机制来应用于所有集合类的遍历。
除了使用Iterator接口遍历集合以外,JDK1.5中又提供了foreach循环语句,以更简单的方法遍历集合和数组。
在后面讲解集合的实现类中,我们再以实例讲解Iterator接口和foreach循环的用法。
在实际应用开发中,很少直接使用Collection接口进行开发,基本都是使用Colleciton接口的子接口。Collection类的主要子接口包括List和Set。下面我们分别讲解这些接口以及接口的常用实现类。

             List接口
List接口继承自Collection接口,它有如下特点:
    List中的元素是有顺序的。
    List通常允许重复元素。
    List的实现类通常支持null元素。
    可以通过索引访问List对象容器中的元素。
当我们使用List接口的实现,并返回对该List中元素的引用时,这些元素以一种可预测的序列来返回。这个序列通过集合中元素的位置来定义,在添加元素时,可以显式或者隐式地指定该位置。除了通过遍历器和foreach循环来连续访问元素外,List允许我们通过指定元素在集合中基于零的位置(也就是索引),来直接引用一个特定的元素。例如,List中的第一个元素的位置是0,第二个元素的位置是1,如此类推。
List接口继承自Collection接口,也就拥有Collection接口的方法。此外,它还重载了Collection接口的一些方法,使程序员可以通过索引值来对列表进行操作。表15-3列出了List接口中在Collection接口新添加的一些常用方法。更多方法,请参考List接口相关API。

在这里插入图片描述

List接口的实现类包括AbstractList、AbstractSequentialList、ArrayList、AttributeList、CopyOnWriteArrayList、LinkedList、RoleList、RoleUnresolvedList、Stack、Vector。实际项目开发中,最常用的是ArrayList、LinkedList。

            ArrayList
ArrayList在概念上与数组类似,表示一组编入索引的元素,区别之处在于ArrayList没有预先确定的大小,其长度可按需增大。
实际上,当我们把对象放入集合后,集合就会忘记这个对象的数据类型;当再次取出对象时,该对象的编译类型就变成了Object类型(当然,运行时类型是没有变化的)。Java集合之所以设计成这样,是因为设计集合的程序员是不可能知道我们需要用它来保存什么类型的对象,所以为通用性着想,他们就将集合设计成能保存任何类型的对象。但是,这样做也带来两个问题:
    集合对元素类型没有任何限制。这样可能会引发一些问题。例如,如果我们想创建一个只能保存Employee类型的集合,但是别人一样可以把一个Integer类型的对象放入进去,所以可能会引发异常。
    由于把对象放入集合后,集合丢失了对象的状态信息,集合只知道它装的是Object,所以取出来之后通常还要进行强制类型转换。这种转换既会增加编程的复杂度,也可能引发ClassCastException异常。
为了解决此类问题,从JDK1.5开始,Java语言中就加入了泛型的功能。
所谓泛型,就是允许在定义类、接口时指定类型形式参数,这个类型形式参数将在声明变量、创建对象时确定(即传入的实际参数)。JDK1.5改写了集合框架中全部接口和类,为这些接口和类提供了泛型支持,从而可以让我们在声明集合变量、创建集合对象时传入类型实际参数。
这里所谓参数类型,不过是占位符而已。使用一个类型参数还是两个类型参数其实没有任何差别。既可以指定一个类型参数(List、Set),也可以指定两个类型参数(Map)。
除了在声明集合类型变量和创建集合对象时,可以用泛型将参数类型化以外,我们还可以用泛型将类方法的形式参数类型化,同时还可以将方法的返回值类型化。
通过泛型将参数类型化以后,我们就可以强迫程序在编译期就检查从集合中取得的数据的类型,以达到类型安全的目的,并省去从集合中取出数据再转型的动作。引入泛型的目的就是为了减少编程时的错误,尽量将错误在编译时就发现,而不是遗留到运行时。

           LinkedList
LinkedList是实现了双向链表功能的列表,它将列表中的每个对象放在独立的空间中,而且每个空间中还保存有上一个和下一个链接的索引,如图15.3所示。JDK5.0开始对LinkedList进行了改写,让它也实现了Queue接口,从而也实现了先进先出(FIFO,First In First Out)的队列数据结构的功能。

在这里插入图片描述

在这里插入图片描述

LinkedList不支持快速随机访问,如果要访问LinkedList中第n个元素,必须从头开始查找,然后跳过前面的n-1个元素。并且,虽然LinkedList也提供了一个get()方法,可以根据指定的索引来获取对应的元素,但是正因为它不支持快速随机访问,所以效率比较低下。

               Set接口
Set接口也继承自Collection接口,同时也继承了Collection接口的全部方法。它有如下特点:
    Set类型容器中不能包含重复元素。当加入一个元素到容器中时候,要比较元素的内容是否存在重复的,所以加入Set类型对象容器的对象必须重写equals()方法。
    元素可能有顺序,也可能没有顺序。
    因为元素可能没有顺序,所以不能基于索引访问Set中的元素。
实现Set接口的类包括AbstractSet、ConcurrentSkipListSet、CopyOnWriteArraySet、 EnumSet、HashSet、JobStateReasons、LinkedHashSet、TreeSet,但是最常用的是HashSet和TreeSet类。
         HashSet 
HashSet类是基于哈希算法的Set接口实现,它主要有如下几个特点:
    当遍历HashSet时,其中的元素是没有顺序的。
    HashSet中不允许出现重复元素。这里的重复元素是指有相同的哈希码,并且用equals()方法进行比较时,返回true的两个对象。
    允许包含null元素。

            TreeSet
TreeSet类不仅实现类Set接口,还实现了SortedSet接口,从而保证集合中的对象按照一定的顺序排序。当向TreeSet集合中添加一个对象时,会把它插入到有序的对象序列中。但是这种排序并不是按照对象添加的顺序排序,而是按照一定的算法来排序。

在这里插入图片描述

TreeSet使用元素的自然顺序对元素进行排序,或者根据创建 set 时提供的 Comparator 进行排序,具体取决于使用的构造方法。也就是说,TreeSet支持自然排序和自定义排序两种排序方式。默认情况下,TreeSet采用自然排序方式。
我们首先来看看什么是TreeSet的自然排序。
在JDK类库中,有一部分类实现了Comparable接口,例如Integer、Double、String等等。Comparable接口在java.lang包中,该接口有一个compareTo(Object o)方法,返回整型数据。对于表达式x.compareTo(y),如果返回值为0,则表示x和y相等;如果返回值大于0,则表示x大于y;如果返回值小于0,则表示x小于y。TreeSet集合调用对象的compareTo()方法比较集合中的大小,然后进行升序排列,这种方式称为自然排序。
对于JDK中常用类,例如Double、Integer、Long、Short、Float等,是按照数字的大小排序。而Character类,按照Unicode值的数字大小进行排序。String对象,按照字符串中字符的Unicode值排序。必须注意的是,使用自然排序时,只能向集合中加入同类型的对象,并且这些对象的类必须实现了Comparable接口,否则程序会抛出异常。
TreeSet的自然排序是根据集合元素的大小,按照升序排列。如果要实现降序排列等非自然排序方式,就要使用自定义排序了。
TreeSet的自定义排序是指对象按照特定的比较器排列,即可以通过比较器Comparator对用TreeSet对象进行排序。如果要实现自定义排序,就必须使用Comparator接口,同时必须改写要存入到TreeSet中的对象的compare(Object o1, Object o2)方法。compare()方法可以用来对两个参数进行对比,如果o1小于o2,则返回-1;如果o1和o2相同,则返回0;如果o1大于o2,则返回1。我们可以重写该方法来定义排序方式。

                     **Map接口**
Map(映射)接口是Java集合框架中不同于Collection接口的另一个重要接口,它对应的是在一种从键(Key)到值(Value)的对应关系的集合。也就是说,Map类型的对象容器里面保存着两组对象,一组对象用于保存Map里的Key,另外一组用于保存Value。Key和Value可以是任何引用类型的数据。Key不能重复,但是Value可以重复。
Key和Value之间存在单向一对一关系,即通过指定的Key,可以找到唯一对应的Value。也就是说,从Map中取出数据时,只要给出指定的Key,就可以取出对应的Value。如果把Map的两组值拆开来看。

在这里插入图片描述

如果把Map中的所有Key放在一起,就组成了一个Set集合(所有Key没有顺序,而且不能重复),实际上Map接口中确实有一个keySet()方法,用于返回Map中所有Key组成的Set对象。而所有Value放在一起,就组成一个可以重复的,没有次序的Collection集合。Map中也提供了一个Values()方法,用于返回Map中所有Value组成的Collection对象。
不仅如此,Map里Key集和Set集合中元素的存储形式也很相像,Map的子接口和实现类与Set的子接口和实现类在名字上也相似。例如,Set下有HashSet、LinkedHashSet、SortedSet、TreeSet等实现类和子接口,Map下则有HashMap、LinkedHashMap、SortedMap、TreeMap等实现类和子接口。正如它们的名字所暗示的,Map中的这些实现类和子接口中Key的存储形式和对应Set集合中元素的存储形式完全相同。
Map接口中定义了Map对象所共有的方法。这些方法基本分为三类操作:基本操作、批量操作、Collection视角的操作。Collection视角的操作提供了将Map转化为Collection的手段。

在这里插入图片描述

                HashMap类
HashMap是基于哈希算法的Map接口的实现。HashMap将它的键保存在哈希表中进行维护,键是唯一的。但是,HashMap并不保证键以特定顺序排列,特别是不保证顺序永久不变。
HashMap类实现了Map接口,从而具有Map接口的所有方法,所以这里我们就不再列出HashMap类的方法。
为了保证HashMap能正常工作, 和HashSet一样,要求当两个键对象通过equals()方法比较为true时,这两个键对象的hashCode()方法返回的哈希码也一样。因此,我们应该在作为Key的类中重写hashCode()和equals()方法。
HashMap使用哈希码通过键对其内容进行快速查找,但是HashMap中的元素是没有顺序的。如果要让Map中的元素按照一定的顺序排列,就要使用TreeMap类。

                    TreeMap类
TreeMap类是基于红黑树算法的Map接口实现。TreeMap中键的存放方式与TreeSet相似,它将键存放在树中,键的顺序按照自然顺序或者自定义顺序两种方式排列。
与HashMap相比,TreeMap因为要考虑排序的问题,所以性能会受到一定影响。如果不需要排序,应该首选HashMap

                       **工具类Collections和Arrays**
Java集合框架中包含了Collections类和Array类这两个功能强大的工具类,这两个类提供了包装器实现、数据结构算法和数组相关的应用。这两个工具类都提供了大量静态方法,在操作集合和数组时,使用这两个工具类中的方法,可以大大减轻程序员编码量,提升开发效率,提高代码性能。

          Collections类
早期Java集合框架中的集合类Vector、Hashtable等都是线程安全的,但是自JDK1.2之后新的集合框架包括(ArrayList、HashSet和HashMap等)都是线程不安全的。如果在程序中有多个线程同时访问新的集合类对象,并且有多个线程试图修改它们,就有可能会出现错误。Collections类中提供了多个静态方法用于创建线程安全的同步集合,包括:
    synchronizedCollection(Collection c):返回一个线程安全的collection。
    synchronizedList(List list):返回一个线程安全的list。
    synchronizedSet(Set s):返回一个线程安全的set。
    synchronizedMap(Map m):返回一个线程安全的map。
此外,Collections类中还提供了unmodifiableXxx()、singletonXxx()、emptyXxx()几个静态方法(这里Xxx代表List、Set、Map等),用于将集合对象设置为不可变的只读集合。如果我们对只读集合进行插入、删除、修改操作,就会抛出一个UnsupportedOperationException异常。
更多Collections类的详细用法,请参考JDK相关说明文档。

             Arrays类
Java集合框架不仅提供了Collections类用于操作集合,同时也提供了Arrays类用于操作数组。Arrays类包含用来操作数组(比如排序和搜索)的各种方法。
  
                **古老的集合类与接口**
JDK类库中存在一些在JDK1.0时候就被开发出来的类和接口,后面新版本的JDK出来后,又对这些类或者接口进行了修改,或者重新开发一套来替代。这其中就包括集合类Vector、Hashtable、Stack、Properties类以及遍历器Enumeration接口。
Vector、Hashtable、Stack和Properties类以及Enumeration接口都是很古老的  集合类和接口,它们在JDK1.0的时候就出现了,那时候Java还没有提供系统的集合框架。
Vector和Hashtable这两个类都有一些方法名很长的方法。从JDK1.2以后,Java就提供了系统的集合框架。但是已经有很多程序是用Vector和Hashtable来编写的,为了保持向后兼容,同时与现有的集合框架保持一致,Java重新设计了Vector和Hashtable,让二者分别实现List接口和Map接口,同时保留以前的方法,这就造成了这两个类中存在一些功能重复的方法。
Vector(向量)与ArrayList的用法几乎完全相同,二者之间的最大区别在于Vector类是线程安全的,而ArrayList不是。因此,Vector在执行效率上会比ArrayList低。如果在实际应用中,不需要考虑多线程问题,就应该优先考虑使用ArrayList,否则就应该使用Vector。
此外,Vector还提供有一个子类Stack。Stack 类表示后进先出(LIFO)的对象堆栈。它通过五个操作对类Vector进行了扩展,允许将向量视为堆栈。它提供了通常的push()和pop()操作,以及取堆栈顶点的peek()方法、测试堆栈是否为空的empty()方法、在堆栈中查找项并确定到堆栈顶距离的search()方法。 Stack类同样是从JDK1.0时出现的,但是JDK1.6提供的Deque 接口及其实现类提供了 LIFO 堆栈操作的更完整和更一致的set,应该优先使用此set。
同样,Hashtable与HashMap的用法也几乎相同,二者的关系完全类似于Vector与ArrayList的关系。Hashtable是线程安全的,而HashMap不是。因此,HashMap执行效率更佳,如果不考虑线程安全问题,就应该优先考虑HashMap。此外,Hashtable不允许key和value为null,而HashMap是允许的。
Hashtable也有一个子类Properties,也是这四个古老集合类中到现在还被频繁使用的一个类。该类用于处理属性文件。所谓属性文件就是将Map形式的键/值对数据存在一个扩展名为“.properties”的文本文件中,常用作软件的配置文件。
在一些简单应用中,我们经常把配置信息存在属性文件中,然后用Properties来对属性文件进行读写。例如,在使用后面要学到的JDBC技术编写数据库应用程序时,我们可以将数据库连接、用户名、密码等写在如上代码所示的属性文件中,这样就不用将这些数据硬编码到程序中,方便测试和部署。
Properties类的主要方法包括:
    String getProperty(String key):根据属性的键key获取属性对应的值。
    String getProperty(String key, String defaultValue):根据属性的key,获取属性的值。如果没有该key,则返回默认值。
    Object setProperty(String key, String value):设置属性。
    void load(InputStream in):从输入流中装载全部属性。
    void store(OutputSteam out, String comments):将属性内容通过输出流输出。
Enumeration接口是JDK1.0就出现的最古老的遍历器,用于遍历Vector、Stack、Hashtable、Properties等JDK1.0遗留下来的集合类。该接口只有两个方法:
    boolean hasMoreElement():测试集合中是否还有更多的元素。如果有则返回true。
    Object nextElement():返回下一个元素。
如果使用新的集合类,就应该采用Iterator遍历器。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值