Java—类集框架

Java的类集框架可以使程序处理对象的方法标准化,类集接口是构造类集框架的基础,使用迭代方法访问类集可以使对类集的操作更高效。

认识类集框架

在基础应用中,通常我们可以通过数组来保存一组具有相同属性的对象或者基本类型的数据,但使用数组的弊端在于其大小是不可更改的,因此出于灵活性的考虑,可以使用链表来实现动态的数组。任何事情都有两面性,灵活性的代价就是操作上的繁琐。在计算机世界里,处理繁琐问题的常用方法就是将其封装,只向外提供可调用的方法视图。Java类集框架就是对这一方法的一种官方实现—套动态对象数组的操作类。本质上,Java类集框架就是Java对数据结构的一个大体上的封装。

类集是在JDK 1.2之后正式提出的概念。从类集开始用户就没有必要再像之前自己来编写链表了。但是类集的内部实现原理依然和之前一样,就是一个动态的对象数组,所不同的是,这个动态数组处理的细节,已被包装屏蔽起来了。这个理念和C++中STL (Standard Template Library,标准模板库)是一脉相承的。

在java.util包之中定义了所有与类集有关的操作接口:Collection、List、Set、Map、Iterator、ListIterator及Enumeration,对于所有给出的接口,要求大家能将其主要的操作方法记下。

在JDK1.5之后,这些接口都增加了泛型的定义,最早的时候这些接口中的内容都使用Object(对象)进行操作。出于安全性的考虑,以及避免转型之间的繁琐,JDK1.5以后将整个类集框架都升级为泛型(Generic programming),极大方便了用户。

在Java中,每个变量都有其所属的数据类型,要么是基本的数据类型(int,float,char等),要么是自定义的数据类型—即类,而泛型的本质就是将变量的“类型”参数化,也就是说所操作的数据类型被指定为一个参数。这种参数类型可以用在类、接口和方法的创建中,分别被称为泛型类、泛型接口、泛型方法。

类集接口

类集框架定义了几个接口。这里对每一个接口进行概述。首先讨论类集接口是因为它们决定了Collection类的基本特性。具体类仅仅是提供了标准接口的不同实现。支持类集的接口总结在下表中。
在这里插入图片描述
除了类集接口之外,类集也需要使用Comparator、Iterator和ListIterator等接口。关于这些接口将在文章后面做更深入的讲解。

在这些接口中定义了操作该类集的一些方法。支持这些方法的类集被称为可修改的(modifiable)。不允许修改其内容的类集被称为不可修改的(unmodifiable)。而所有内置的类集都是可修改的。如果对一个不可修改的类集使用这些方法,将引发一个UnsupportedOperationException异常。

单值保存的最大父接口—Collection

Collection接口是构造类集框架的基础,是单值数据操作的最大父接口,它声明了所有类集都将拥有的核心方法,这些方法的简明含义如下表所示。由于所有类集均实现了Collection,所以熟悉它的方法对于清楚地理解框架是必要的。其中有几种方法可能会引发一个UnsupportedOperationException异常。正如上面解释的那样,这些异常将发生在修改不能被修改的类集的时候。当一个对象与另一个对象不兼容,例如企图增加一个不兼容的对象到一个类集中时,将产生一个ClassCastException异常。
在这里插入图片描述
在Collection接口之中所提供的方法一定是在日后进行程序开发中使用最多的方法,但是Collection接口很少在开发之中直接去使用,往往都会使用它的子接口:List(允许有重复元素)、Set(不允许有重复元素)。

Collection接口的具体实现类

标准的Collection实现类总结如下表所示。
在这里插入图片描述
提示
除了Collection接口之外,还有几个从以前版本遗留下来的类,如Vector、Stack和Hashtable等均被重新设计成支持类集的形式。这些内容将在本章后面讨论。下面讨论具体的Collection接口,并举例说明它们的用法。

允许重复的子接口—List

List(列表)是Collection接口之中最为常用的一个子接口,首先来观察一下List子接口的定义。
在这里插入图片描述
List子接口对Collection接口进行了大量的扩充。List接口扩展了Collection并声明存储一系列元素的类集的特性。使用一个基于零的下标,元素可以通过它们在列表中的位置被插入和访问。

一个列表可以包含重复的元素,即可以存在完全相同的两个元素。除了由Collection定义的方法之外,List还定义了一些它自己的方法,这些方法总结在下表中。需要注意的是,当类集不能被修改时,其中的几种方法将引发UnsupportedOperationException异常。当一个对象与另一个不兼容时,例如企图将一个不兼容的对象加入一个类集中,将产生ClassCastException异常。
在这里插入图片描述
对 于 由Collection定 义 的add( ) 和addAll( )方 法,List增 加 了 方 法add(int,Object) 和addAll(int, Collection),这些方法在指定的下标处插入元素。由Collection定义的add(Object)和addAll(Collection)的语义也被List改变了,以便它们在列表的尾部增加元素。

为了获得在指定位置存储的对象,可以用对象的下标调用get( )方法。为了给类集中的一个元素赋值,可以调用set( )方法,指定被改变的对象的下标。调用indexOf( )或lastIndexOf( )可以得到一个对象的下标。通过调用subList( )方法,可以获得列表的一个指定了开始下标和结束下标的子列表。

由于List本身毕竟还属于接口,要想使用接口,就必须知道实现这个接口的子类,在List接口中有两个最为常用的子类:ArrayList、Vector。

ArrayList类

ArrayList类扩展AbstractList并执行List接口。ArrayList支持可随需要而增长的动态数组。在Java中,标准类型的数组是定长的。一旦数组被创建之后,它们不能被加长或缩短,这也就意味着开发者必须事先知道数组可以容纳多少元素。

但是在一般情况下,只有在运行时才能知道需要多大的数组。为了解决这个问题,类集框架定义了ArrayList。本质上,ArrayList是对象引用的一个变长数组。也就是说,ArrayList能够动态地增加或减小其大小。数组列表以一个原始大小被创建。当超过了它的大小时,类集就会自动增大。当有对象被删除,数组就可以缩小。注意:动态数组也被从以前版本遗留下来的类Vector所支持。关于这一点将在后面介绍。

ArrayList有如下的构造方法。
在这里插入图片描述
其中第1个构造方法构造一个初始容量为 10 的空列表。第2个构造方法建立一个数组列表,该数组列表由类c中的元素初始化。第3个构造方法建立一个数组列表,该数组有指定的初始容量(capacity),容量是用于存储元素的基本数组的大小。当元素被追加到数组列表上时,容量会自动增加。

下面的程序是ArrayList的一个简单应用。首先创建一个数组列表,接着添加String类型的对象【回想一个引用字符串被转换成一个字符串(String)对象的方法】,接着列表被显示出来。将其中的一些元素删除后,则再一次显示列表。

ArrayList类使用范例1(ArrayListDemo1.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第07行的一对尖括号“<>”中的String,表明ArrayList的数组链表对象 al,这个数组链表中的元素为String类型。尖括号“<>”内的数据类型是可变的,除了String类型,还可以是其他类型如Integer等,这表明变量类型也是可变的—这就是Java的泛型的应用。第10~15行,利用add()方法依次顺序添加元素。第17行,使用add()方法在指定位置添加元素。第22行,利用remove()方法删除某个指定内容(“F”)的元素,第23行,利用remove()方法删除某个指定位置(本例为2)的元素。

尽管当对象被存储在ArrayList对象中时,其容量会自动增加。然而,也可以通过调用ensureCapacity( )方法来人工地增加ArrayList的容量。如果事先知道将在当前的类集中存储大量数据时,大家可能会这样做。在开始时,通过一次性地增加它的容量,就能避免后面的再分配。因为再分配是很花时间的,避免不必要的处理可以提高性能。

ArrayList类使用范例2(ArrayListToArray.java)
在这里插入图片描述
在这里插入图片描述
第05行创建一个Interger(整型)类型的ArrayList对象a1,需要读者注意的是,尖括号“<>”内的类型是Interger—个整型对象,而不是int—个基本数据类型(整型)。

更为专业的说法是,第05行创建了一个整数的类集。由于不能将基本类型存储在类集中,因此需要创建Integer类型的对象并被保存。接下来,第13行的toArray( )方法被调用,它获得了一个Object对象数组ia,这个数组的内容被置成整型(Integer),第16~18行对ia数组中值进行求和操作。其中第17行,先将Object类型的对象数组ia被强制转换为Interger类型,然后利用intValue()来读取Integer对象中的值。

LinkedList类

LinkedList类扩展了AbstractSequentialList类并实现List接口。它提供了一个链接列表的数据结构。它具有如下的两个构造方法。
在这里插入图片描述
第1个构造方法建立一个空的链接列表。第2个构造方法建立一个链接列表,该链接列表由类c中的元素初始化。

除了它继承的方法之外,LinkedList类本身还定义了一些有用的方法,这些方法主要用于操作和访问列表。使用addFirst( )方法可以在列表头增加元素,使用addLast( )方法可以在列表的尾部增加元素。它们的形式如下所示。
在这里插入图片描述
在这里,obj是被增加的对象。

调用getFirst( )方法可以获得第1个元素,调用getLast( )方法可以得到最后一个元素。它们的形式如下所示。
在这里插入图片描述
为了删除第1个元素,可以使用removeFirst( )方法。删除最后一个元素,可以调用removeLast( )方法。它们的形式如下所示。
在这里插入图片描述
下面的程序是对LinkedList中的这几个方法的使用范例演示。

LinkedList类的使用(LinkedListDemo.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第05行创建了一个LinkedList类型对象 LL,这个链接链表中的元素类型为String。因为LinkedList实现List接口,因此调用add(Object)将项目追加到列表的尾部(第07~11行),就如同addLast( )方法所做的那样(第13行)。注意到第07和第08行添加了两个相同的元素“F”,这验证了List中允许有重复的元素。

使用add( )方法的add(int, Object)形式,插入项目到指定的位置,如范例程序第17行中调用add (1,“A2”)的举例。第20行演示了利用remove()方法从LinkedList中移除元素“F”,由于本例中链表LL中有两个“F”,这里仅仅删除链表中首次找到的元素,并非将所有“F”元素都删除。第22行演示了在指定位置(从0开始计数,这里是将第2个元素删除)。第25和26行分别将第一个和最后一个元素删除。第29行,用get()方法将获得指定位置的元素,第30行用set()方法在指定位置设置元素的值。

旧的子类——Vector

Vector实现动态数组,这与ArrayList相似,但两者不同的是:Vector是同步的,并且它包含了许多不属于类集框架的从以前版本遗留下来的方法。随着Java 2的公布,Vector被重新设计来扩展AbstractList和实现List接口,因此现在它与类集是完全兼容的。

下面列出Vector的构造方法。
在这里插入图片描述
第1种形式创建一个原始大小为10的默认矢量。第2种形式创建一个其原始容量由size指定的矢量。第3种形式创建一个其原始容量由size指定,并且它的增量由incr指定的矢量,增量指定了矢量每次允许向上改变大小的元素的个数。第4种形式创建一个包含了类集c中元素的矢量。这个构造方法是在Java 2中新增加的。

所有的矢量开始都有一个原始的容量。在这个原始容量达到以后,下一次再试图向矢量中存储对象时,矢量会自动为那个对象分配空间,同时为别的对象增加额外的空间。通过分配超过需要的内存,矢量减小了可能产生的分配的次数。这种次数的减少是很重要的,因为分配内存是很花时间的。在每一次的再分配中,分配的额外空间的总数由在创建矢量时指定的增量来确定。如果没有指定增量,在每个分配周期,矢量的大小增加一倍。

Vector定义了下面的保护数据成员。
在这里插入图片描述
增量值被存储在capacityIncrement中,矢量中的当前元素的个数被存储在elementCount中,保存矢量的数组被存储在elementData中。

除了由List定义的类集方法之外,Vector还定义了几个从以前版本遗留下来的方法,这些方法列在下表中。
在这里插入图片描述
在这里插入图片描述
因为Vector实现List,所以可以像使用ArrayList的一个实例那样使用矢量。也可以使用它的从以前版本遗留下来的方法来操作它。例如,在后面实例化Vector,可以通过调用addElement( )方法而为其增加一个元素。调用elementAt( )方法可以获得指定位置处的元素。

调用firstElement( )方法可以得到矢量的第1个元素,调用lastElement( )方法可以检索到矢量的最后一个元素,使用indexOf( ) 和lastIndexOf( ) 方法可以获得元素的下标,调用removeElement( )或removeElementAt( )方法可以删除元素。

下面的程序使用矢量存储不同类型的数值对象。程序说明了几种由Vector定义的从以前版本遗留下来的方法,同时它也说明了枚举(Enumeration)接口

使用矢量存储不同类型的数值对象(VectorDemo.java)
在这里插入图片描述
在这里插入图片描述
第06行,定义了一个Vector对象v,v中的元素为String类型。第07~12行利用add()增加6个元素。第13行,定义了一个枚举对象e,e中的元素也为String类型,用于接收Vector中的元素。14~17行的while循环输出枚举中的元素,这里用到了枚举的方法nextElement()——用于获得枚举中的下一个元素。

在Java 2之后,Vector增加了对迭代(Iterator)方法的支持。现在可以使用迭代方法来替代枚举去遍历对象(正如前面的程序所做的那样)。例如,下面的基于迭代方法的程序代码可以被替换到上面的程序中。
在这里插入图片描述

数组操作类——Arrays

在本质上,类集本身是一个对象数组。那么在之前曾经学习过一个java.util.Arrays类,这个类是可以操作数组的。Arrays类数组操作类,可用来操作数组(如数组元素排序、搜索和填充等)的各种方法。Arrays类的常用方法如下表所示。
在这里插入图片描述
使用数组操作类Arrays的使用(ArrayDemo.java)
在这里插入图片描述
在这里插入图片描述
第07行,使用Arrays中的静态方法asList(),将括号()内的四个字符串转换为一个List,并赋值给List对象all。第08行输出all中的所有元素。

在Arrays类之中定义了许多的数组操作方法,例如:二分查找binarySearch()、(并行)排序parallelSort()/sort()、比较两个数组是否相等equals()、填充fill()等。大家在使用这些方法时,需要读者参阅相关文档来了解相关使用细节。

数组操作类Arrays排序及二分查找方法的使用(ArrayDemo2.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第05行,定义了一个整型数组arrInt。第07行,使用Arrays类中的sort方法对arrInt实施升序排序。第18行,利用二分查找法查找指定的整数。

使用Scanner类(是SDK1.5后增加的一个类)定义一个读取控制台输入的对象scan。然后scan对象调用下列方法(函数),读取用户在命令行输入的各种数据类型:next.Byte()、nextDouble()、nextFloat、nextInt()、nextLin()、nextLong()及nextShot() 等。当通过new Scanner(System.in)创建一个Scanner后,控制台会一直等待输入,直到敲回车键结束,把所输入的内容传给Scanner,作为扫描对象。第11行调用Scanner中的nextInt()方法读取控制台输入的整数。

比较器

需要为多个对象排序时必须设置排序规则,而排序规则就可以通过比较器进行设置,而在Java之中比较器提供了两种:Comparable和Comparator。

Comparable接口

Comparable是一个要进行多个对象比较的类需要默认实现的一个接口,这个接口的定义如下。
在这里插入图片描述
从接口的定义格式上来看,可以发现如果想实现对象属性的排序功能,要实现Comparable接口,并覆写compareTo(To)方法。此方法返回的是一个int类型的数据,该返回值只能是以下三种情况之一。

⑴ 相等:0;

⑵ 大于:1;

⑶ 小于:-1。

使用Comparable接口解决问题(comparableDemo.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
代码第11行和第15行,是Java的注解,标识下面的方法为覆写。第16~24行就是覆写了compareTo()方法。这个方法对本对象(this)的price和方法参数中的Book对象的价格实施比较。该方法只能返回规定的值:1、-1和0。

第28行创建List对象bookList,这个列表中的每个元素都是一个Book对象。第29~32行,利用List的add()方法,添加了4个Book对象。第33行,利用toArray()方法将List中元素转换为一个Book对象数组。第34行用Arrays类中静态排序方法为对象数组排序。第35行输出该数组对象中的值。

挽救的比较器接口——Comparator

Comparable接口是在一个类定义的时候就已经默认实现好的功能了。例如在【范例 使用Comparable接口解决问题(comparableDemo.java)】中,在定义类Book时,就实现了Comparable接口(04行),并在第16~24行就是覆写了compareTo()方法。所以,后续的代码使用类Book时,对Book类的对象数组实施排序,就是顺理成章的事。

但是,如果说现在假设有一个类已经开发完,在此类使用了很久之后,忽然有一天需要实现对这个类对象数组的排序,但是由于此类并没有实现Comparable接口,所以现在一定无法利用Arrays类的sort()方法完成排序,并且这个类由于某些原因也无法再进行修改了。在这种情况下如果还想完成排序的功能,那该怎么办?

这时就必须进行另外一种比较规则的设置,即:挽救比较规则,利用java.util.Comparator接口实现,Comparator接口定义如下。
在这里插入图片描述
Comparator接口定义了两个方法。compare( )和equals( )。这里给出的compare( )方法按顺序比较了两个元素。
在这里插入图片描述
obj1和obj2是被比较的两个对象。当两个对象相等时,该方法返回0;当obj1大于obj2时,返回一个正值;否则,返回一个负值。如果用于比较的对象的类型不兼容的话,该方法会引发一个ClassCastException异常。通过改写compare( ),可以改变对象排序的方式。例如,通过创建一个颠倒比较输出的比较方法,可以实现按逆向排序。

这里给出的equals( )方法,用于测试一个对象是否与调用比较方法相等。
在这里插入图片描述
obj是被用来进行相等测试的对象。如果obj和调用对象都是Comparator的对象,并且使用相同的排序,该方法则返回true,否则返回false。

假设,一个定义完整但没有实现的Comparable接口的Book类如下所示。
在这里插入图片描述
现在需要单独定义一个类,来实现Comparator接口,专门为Book类对象数组提供比较规则。这个额外提供的类BookComparator定义如下所示。
在这里插入图片描述
在BookComparator类中,覆写了compare(),在这个方法里,有两个Book对象,用于读取这两个对象的价格,然后进行比较,该方法的返回值依然限制为1、-1和0。

如果现在要使用以上的规则,则必须使用Arrays类的另外一个对象数组排序方法。
在这里插入图片描述
结合上面的代码铺垫,这时我们就可以完成Book对象数组的排序了,参见下面的范例。

Comparator实现对象数组排序(comparatorDemo.java)。
在这里插入图片描述
在这里插入图片描述
第06~10行创建一个Book类对象数组book,第11行用Arrays类中的静态sort()方法对book数组进行排序,注意到这个sort()方法的第二个参数是BookComparator对象,它定义了数组的排序规则。

不允许重复的子接口—Set

Set也是一个非常重要的接口,但是Set接口并不像List接口那样,对Collection接口进行了大量的扩充,而是完整地继承下了Collection接口。

集合接口定义了一个集合,并添加了类集中元素不允许重复的特性,即在set中不能出现完全相同的两个元素。因此,如果试图将复制元素加到集合中时,add()方法将返回false。它本身并没有定义任何附加的方法。在Set接口中也有两个常用的子类:HashSet、TreeSet。

HashSet类

HashSet扩展自bstractSet并且实现了Set接口。它创建一个类集,该类集使用散列表进行存储,而散列表则通过使用称之为散列法的机制来存储信息。HashSet里面所保存的数据是不能够有重复的,并且没有顺序。

在散列(hashing)中,一个关键字的信息内容被用来确定唯一的一个值,称为散列码(hashcode),而散列码则被用来当做与关键字相连的数据的存储下标。关键字到其散列码的转换是自动执行的—看不到散列码本身。程序代码也不能直接索引散列表。散列法的优点在于即使对于大的集合,它也允许一些基本操作,如add()、contains()、remove()和size( )等方法的运行时间保持不变。HashSet没有定义任何超类和接口之外的其他方法。

下面的构造方法定义为:
在这里插入图片描述
第1种形式构造一个默认的散列集合,第2种形式用c中的元素初始化散列集合,第3种形式用capacity初始化散列集合的容量,第4种形式用它的参数初始化散列集合的容量和填充比(也称为加载容量),填充比必须介于0.0与1.0之间,它决定在散列集合向上调整大小之前,有多少空间被充满。具体来说,就是当元素的个数大于散列集合容量乘以它的填充比时,散列集合将被扩大。对于没有获得填充比的构造方法,默认为0.75。

重要的是,注意散列集合并不能确定其元素的排列顺序,因为散列法的处理通常不让自己参与创建排序集合。如果需要排序存储,另一种类集—TreeSet将是一个更好的选择。

HashSet类的使用(HashSetDemo.java)。
在这里插入图片描述
在这里插入图片描述
第07行创建了HashSet对象 hs,hs中的元素为String类型。第09~14行添加若干元素至hs中。从输出的结果可以看出,如上面解释的那样,元素并没有按顺序进行存储。

TreeSet类

如果现在需要为保存的数据进行排序,那么就使用TreeSet子类完成。TreeSet为使用树来进行存储的Set接口提供了一个工具,对象按升序存储。访问和检索是很快的。在存储了大量的需要进行快速检索的排序信息的情况下,TreeSet是一个很好的选择。

下面的构造方法定义如下所示。
在这里插入图片描述
第1种形式构造一个空的树集合,该树集合将根据其元素的自然顺序按升序排序。第2种形式构造一个包含了c的元素的树集合。第3种形式构造一个空的树集合,它按照由comp指定的比较方法进行排序(比较方法将在文章后面介绍)。第4种形式构造一个包含了ss的元素的树集合。下面是一个TreeSet的使用范例。

TreeSet的使用(TreeSetDemo.java)
在这里插入图片描述
在这里插入图片描述
第09~14行分别添加String类型的元素:C,A,B,E,F,D。正如上面解释的那样,因为TreeSet按树存储其元素,因此它们被按照排序次序自动排序,如上图所示。

SortedSet接口

SortedSet接口扩展了Set并说明了按升序排列的集合的特性。除了那些由Set定义的方法之外,由SortedSet接口说明的方法列在下表中。当没有项包含在调用集合中时,其中的几种方法会引发NoSuchElementException异常。当对象与调用集合中的元素不兼容时,将引发ClassCastException 异常。如果试图使用null 对象,而集合不允许null 时,会引发NullPointerException异常。
在这里插入图片描述
SortedSet定义了几种方法,使得对集合的处理更加方便。调用first()方法,可以获得集合中的第一个对象。调用last()方法,可以获得集合中的最后一个元素。调用subSet()方法,可以获得排序集合的一个指定了第一个和最后一个对象的子集合。如果需要得到从集合的第1个元素开始的一个子集合,可以使用headSet()方法。如果需要获得集合尾部的一个子集合,可使用tailSet()方法。

类集的输出

通常开发者希望通过循环输出类集中的元素。例如,可能会希望显示每一个元素。处理这个问题最简单的方法是使用iterator(),该方法返回一个对象,或用于实现一个单向迭代输出的Iterator,或用于实现双向迭代输出的ListIterator接口。

迭代器

泛型编程(Generic Programming)倡导用通用的方式进行编程。Java通过泛型机制实现了算法与数据类型的无关性以及容器(数据结构)与数据类型的无关性,但是泛型机制无法解决算法与容器的分离问题。为此,Java中引入了迭代器技术。迭代器(Iterator)是一种抽象的设计概念,它提供了一种方法允许依序访问某个容器所含的各个元素,而无需暴露该容器的内部结构。迭代器又称迭代子,提供了对一个容器内对象的访问方法,并且定义了容器中对象的范围。

迭代器(Iterator)是一种设计模式,在Java中,它是一个对象,它的任务就是遍历并选择序列中的对象,而开发人员不需要了解该序列的底层结构。只要拿到这个对象,使用迭代器就可以遍历这个对象的内部。迭代器通常被称为“轻量级”对象,因为创建它的代价很小。

单向迭代输出——Iterator

Iterator可以完成通过循环输出类集内容,从而获得或删除元素。Iterator是在进行集合输出的过程之中最为常见的一种输出接口,这个接口的定义如下。
在这里插入图片描述
Iterator本身属于一个接口,如果要想取得这个接口的实例化对象,则必须依靠Collection接口中定义的一个方法。public Iterator iterator()。下面介绍使用迭代的方法。

在通过迭代方法访问类集之前,必须得到一个迭代方法。每一个Collection类都提供一个iterator( )方法,该方法返回一个对类集的迭代方法。通过使用这个迭代方法对象,可以一次一个地访问类集中的每一个元素。通常,使用迭代方法通过循环输出类集的内容,具体的操作步骤如下。

⑴ 通过调用类集的iterator( )方法获得对类集的迭代方法。

⑵ 建立一个调用hasNext( )方法的循环,只要hasNext( )返回true,就进行循环迭代。

⑶ 在循环内部,通过调用next( )方法来得到每一个元素。

通过迭代方法访问类集(IteratorDemo.java)。
在这里插入图片描述
在这里插入图片描述
第05行创建一个ArrayList数组al, 数组的元素为String类型。第07~09行分别添加“Welcome”,“to”和“HAUT”等3个元素到ArrayList中。第12行,利用iterator()返回al的迭代器,并赋值给itr。这里迭代器的作用类似于C语言中的指针,可以逐一顺序用于访问数组中的每个元素。如果存在下一个元素,hasNext( )方法会返回true,否则返回false。当Iterator到达列表末端时, hasNext( )方法会返回false,第13行的while循环条件就会终止。

双向迭代——ListIterator

ListIterator扩展了Iterator,允许双向遍历列表,并且可以修改单元。对于执行List的类集,也可以通过调用ListIterator来获得迭代方法。列表迭代方法提供了前向或后向访问类集的能力,并且可以修改元素,否则ListIterator如同Iterator功能一样。ListIterator接口说明的方法总结在下表中。
在这里插入图片描述
通过双向迭代方法访问类集(ListIteratorDemo.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第07~09行分别添加“Welcome”,“to”和“HAUT”等3个元素到ArrayList中。第11行定义了双向迭代器litr。第12~16行所示的while循环中,利用litr的next()方法获取ArrayList中元素——String字符串,并用set()方法修改每个元素的值——将每个字符串元素添加一个加号“+”。

第20~23行中的while循环中,反向输出ArrayList的元素。为了反向遍历列表,程序使用litr,但这一次,程序将检测它是否有前一个元素,使用hasPrevious()方法,如果存在前一个元素,则返回true,否则返回false(这是第20行的while循环的终止条件)。

需要大家特别注意的是,第12~16行的while循环除了逐个修改了ArrayList中元素,还将双向迭代器litr移到了ArrayList的尾部,这样才能完成第20~23行中的while循环中的反向输出。也就是说,如果现在要想完成由后向前的输出操作,那么首先必须完成由前向后的输出(或操作)——即保证反向输出前,迭代器要位于ArrayList的尾部。

大家可以将代码第12~16行注释掉,然后运行程序,就会发现程序依然可以正常运行,但什么也不会输出,请思考为什么?

废弃的枚举输出——Enumeration

如果按照历史来讲,Enumeration属于最古老的输出接口之一,在JDK 1.0时就已经提出了,并一直延续着使用了很长时间,直到今天还有许多的类只支持Enumeration输出。

Enumeration接口定义了可以对一个对象的类集中的元素进行枚举(一次获得一个)的方法。这个接口尽管没有被摈弃,但已经被Iterator所替代。Enumeration对新程序来说是过时的。然而它仍被几种从以前版本遗留下来的类(例如Vector和Properties)所定义的方法使用,被几种其他的API类所使用,以及被目前广泛使用的应用程序所使用。

在JDK 1.5之后为Enumeration增加了泛型的定义,此接口定义如下。
在这里插入图片描述
这个接口只是负责输出两个方法,作用如下。

⑴ 判断是否有下一个元素:public boolean hasMoreElements();

⑵ 取得下一个元素:public E nextElement()。

执行后,当仍有更多的元素可提取时,hasMoreElements( )方法一定返回true。当所有元素都被枚举了,则返回false。nextElement( )方法将枚举中的下一个对象作为一个类属Object的引用而返回。也就是每次调用nextElement( )方法获得枚举中的下一个对象。

可是如果要想取得Enumeration接口对象并不是依靠Collection、List、Set这样的接口,只能够依靠Vector子类,在这个类中定义了一个方法。publicEnumeration elements()。

通过双向迭代方法访问类集(EnumerationDemo.java)
在这里插入图片描述
在这里插入图片描述
第05行创建一个Vector对象all,其元素为String。第06~08行利用add()方法向all添加三个元素。第09行创建Enumeration 对象 enu,其内容为来自Vector对象all(使用elements()方法获取all的所有元素)。第10~13行的while循环获取枚举元素,并输出。其中hasMoreElements()方法来判断是否有更多的元素(第10行)。第11行的nextElement()方法用于获取枚举中的下一个元素。

在大部分情况下,我们推荐读者使用迭代器Iterator,而Enumeration的使用毕竟是少数,只在不得已时才会使用它。

for-each输出

使用for-each不仅可以进行数组的输出,也可以进行集合的输出。请参见下面的范例。

使用foreach输出。
在这里插入图片描述
在这里插入图片描述
第07~13行使用foreach结构输出List对象all中的三个元素。此种方式实际上并不利于理解集合的标准输出格式,而且一旦不理解输出格式,那么对于日后学习之中的很多操作原理就无法清楚,所以不建议这样使用,遇见集合还是考虑Iterator。

偶对象保存接口——Map

在java.util中还增加了映射(Map)。映射是一个存储关键字和值的关联,或者说是“关键字/值”对的对象,即给定一个关键字,可以得到它的值。关键字和值都是对象,关键字必须是唯一的,但是可以存在相同的值。有的映射可以接收null关键字和null值,有的则不能。

可以将Map视为偶对象保存接口。Collection每一次只保存一个对象,而Map保存的是一对对象,而且这一对对象一定是按照“Key = Value”的形式保存,也就是说,可以通过Key找到对应的Value,那么这就好比使用电话本一样:

⑴ Key = 张三,Value = 110;

⑵ Key = 李四,Value = 120;

如果说现在要想找到张三的电话,那么首先应该通过张三这个Key,然后找到其对应的Value——110,如果现在保存的数据之中没有对应的Key,那么就返回null。

映射接口

正因为映射接口定义了映射的特征和本质,所以从这里开始讨论映射。表中所列为支持映射的接口。
在这里插入图片描述
下面对每个接口依次进行讨论。

  1. Map 接口

Map接口映射唯一关键字到值。关键字(key)是以后用于检索值的对象。给定一个关键字和一个值,可以存储这个值到一个Map对象中。当这个值被存储以后,就可以使用它的关键字来检索它。Map的方法总结在下表中。当调用的映射中没有项存在时,其中的几种方法会引发一个NoSuchElementException异常。而当对象与映射中的元素不兼容时,则会引发一个ClassCastException异常。如果试图使用映射不允许使用的null对象,则会引发一个NullPointerException异常。当试图改变一个不允许修改的映射时,则会引发一个UnsupportedOperationException异常。
在这里插入图片描述
需要注意的是,映射不是类集,但可以获得映射的类集“视图”。为了实现这种功能,可以使用entrySet( )方法,它返回一个包含了映射中元素的集合(Set)。为了得到关键字的类集“视图”,可以使用keySet( )方法。为了得到值的类集“视图”,可以使用values()方法。类集“视图”是将映射集成到类集框架内的手段。

  1. SortedMap 接口

SortedMap接口扩展了Map,它确保了各项按关键字升序排序。由SortedMap说明的方法总结在下表中。当调用映射中没有的项时,其中的几种方法将引发一个NoSuchElementException异常。当对象与映射中的元素不兼容时,则会引发一个ClassCastException异常。当试图使用映射不允许使用的null对象时,则会引发一个nullPointerException异常。
在这里插入图片描述
3. Map.Entry 接口

Map.Entry接口使得可以操作映射的输入。回想由Map接口说明的entrySet( )方法,调用该方法可返回一个包含映射输入的集合(Set),这些集合元素的每一个都是一个Map.Entry对象。下表总结了由该接口说明的方法。
在这里插入图片描述

映射类

有几个类提供了映射接口的实现。可以被用做映射的类如下表所示。
在这里插入图片描述
提示
AbstractMap对3个具体的映射实现来说,是一个超类。AbstractMap的另一个子类——WeakHashMap实现一个使用“弱关键字”的映射,它允许映射中的元素,当该映射的关键字不再被使用时,被放入回收站。关于这个类在这里不做更深入的讨论,其他的类将在下面介绍。

  1. HashMap 类

HashMap类使用散列表实现Map接口,它是Map接口中最为常用的子类。HashMap允许一些基本操作,如get( )和put( )的运行时间保持恒定,即便对大型的集合也是这样的。下面的构造方法定义为:

⑴ HashMap( )。

⑵ HashMap(Map m)。

⑶ HashMap(int capacity)。

⑷ HashMap(int capacity, float fillRatio)。

第1种形式构造一个默认的散列映射。第2种形式用m的元素初始化散列映射。第3种形式将散列映射的容量初始化为capacity。第4种形式用它的参数同时初始化散列映射的容量和填充比。容量和填充比的含义与前面介绍的HashSet中的容量和填充比相同。

HashMap实现Map并扩展AbstractMap。它本身并没有增加任何新的方法。应该注意的是:散列映射并不保证它的元素的顺序。因此,元素加入散列映射的顺序并不一定是它们被迭代方法读出的顺序。

下面的程序举例说明了HashMap。它将名字映射到账目资产平衡表。应注意集合“视图”是如何获得和被使用的。

将名字映射到账目资产平衡表(HashMapDemo.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第08行, 创建HashMap对象hm,其中尖括号内”< String, Double >”,表明Key为String类型,而Value为Double(注意类型的大小写,首字母大写的Double是一个双精度的对象类型,而首字母小写的double属于基本数据类型)。第10~14行,利用put()方法将一系列”< String, Double >”对添加至哈希映射中。以第10行为例,由于本例的哈希映射的第二个参数为Double类型,所以不能直接用基本数据类型“3434.34”作为参数,而是将其用作Double类的构造方法的参数,来构造一个新的Double类型的无名对象。

第16行,返回包含映射中项的集合。等号左边的第一层尖括号表明,集合Set之中元素的泛型类型为Map.Entry,而第二层的尖括号表明这个映射的Key为String类型,而Value为Double类型。第18行定义迭代器i,用于方便取得HashMap中的内容。

第23行使用Map.Entry 类来定义对象me。Entry对应的中文为“实体”,这里的实体是用尖括号<String, Double>定义的类型。使用Map.Entry 类的优势在于:如果仅仅用Map来取值,其过程是先从Map中取得关键字之后,我们必须每次重复返回到Map中取得相对的值,这个过程很繁琐和费时。而使用Map.Entry类,可以一次性地得到所有的Map中的信息。第31行,用新的值替换掉旧的值,这时“JohnDoe”作为HashMap的关键字(Key)来辅助索引,从而方便更新对应的Value值。

程序开始创建了一个散列映射,然后将名字的映射增加到平衡表中。接下来,映射的内容通过使用由调用方法entrySet( )而获得的集合“视图”而显示出来。关键字和值通过调用由Map.Entry定义的getKey( )和getValue( )方法而显示。注意存款是如何被制成John Doe的账目的。put( )方法自动用新值替换与指定关键字相关联的原先的值。因此,在John Doe的账目被更新后,散列映射则仍然仅仅保留一个“John Doe”账目。

  1. TreeMap 类

TreeMap类是基于红黑树(Red-Black tree)实现Map接口。TreeMap提供了按排序顺序存储关键字/值对的有效手段,同时允许快速检索。应该注意的是,不像散列映射,树映射保证它的元素按照关键字升序排序。

下面的TreeMap构造方法定义为。

⑴ TreeMap( )。

⑵ TreeMap(Comparator comp)。

⑶ TreeMap(Map m)。

⑷ TreeMap(SortedMap sm)。

第1种形式构造一个空的树映射,该映射使用其关键字的自然顺序来排序。第2种形式构造一个空的基于树的映射,该映射通过使用Comparator comp来排序(比较方法Comparator将在本章后面讨论)。第3种形式用从m的输入初始化树映射,该映射使用关键字的自然顺序来排序。第4种形式用从sm的输入来初始化一个树映射,该映射将按与sm相同的顺序来排序。

TreeMap实现SortedMap并且扩展AbstractMap,而它本身并没有另外定义其他的方法。

TreeMap的使用(TreeMapDemo.java)

import Java.util.*;
public class TreeMapDemo {
	public static void main(String args[]){
	//创建TreeMap对象
		TreeMap<Integer,String> tm = new TreeMap<Integer,String>();
		//加入元素到TreeMap中
		tm.put(new Integer(10000 - 2000),"张三");
		tm.put( new Integer( 10000 - 1500 ), "李四" );
		tm.put( new Integer( 10000 - 2500 ), "王五" );
		tm.put( new Integer( 10000 - 5000 ), "赵六" );
		Collection<String> col = tm.values();
		Iterator<String> i = col.iterator();
	    System。out.println("按工资由低到高顺序输出:");
		while(i.hasNext(){
			System.out.println(i.next());
		}
	  }
   }

在这里插入图片描述
注意到本例,默认是对关键字(Key)——这里指的是工资进行了排序。然而在某些特殊情况下,我们可能需要用哈希表的Value(本例为姓名)来排序,这时就需要指定一个比较方法来改变这种排序。

比较方法

TreeSet和TreeMap都按排序顺序存储元素。然而,更为“个性化”的排序顺序则需使用特定的比较方法。通常在默认的情况下,这些类通过使用被Java称之为“自然顺序”的顺序存储它们的元素,而这种顺序通常也是你所需要的(A在B的前面,1在2的前面,等等)。如果需要用不同的方法对元素进行排序,可以在构造集合或映射时,指定一个Comparator对象。这样做为开发者提供了一种精确控制如何将元素储存到排序类集和映射中的能力。

下面是一个说明定制的比较方法能力的例子。该例子实现compare( )方法以便它按照正常顺序的逆向进行操作。因此,它使得一个树集合按逆向的顺序进行存储。

定制的比较方法能力(ComparatorDemo.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
仔细观察实现Comparator并覆写compare()方法的MyComp类(正如前面所解释的那样,覆写equals( )方法既不是必需的,也不是常用的)。在compare( )方法内部,String方法compareTo( )比较两个字符串。然而由bStr—而不是aStr—调用compareTo( )方法,会导致比较的结果被逆向。

对应一个更实际的例子,下面是用TreeMap程序实现存储账目资产平衡表例子的程序。下面的程序按姓对账目进行排序。为了实现这种功能,程序使用了比较方法来比较每一个账目下姓的先后顺序,得到的映射是按姓进行排序的。

使用TreeMap程序实现存储账目资产平衡表(TreeMapDemo2.java)。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第19~25行定义了TreeMap的比较器,并在04~15行实现了Comparator接口的compare()方法,第19行通过自定义比较器创建了TreeMap对象tm。21~25行向TreeMap中添加了5个元素。26行通过entrySet()方法获得TreeMap的set视图。并在27行通过iterator()方法获得set集合的迭代器。

在28~32行通过while输出TreeMap中的内容。在34行测试了通过get()方法和xxxValue()方法获得TreeMap中指定键的值,这里的xxx通常为某种基本的数据类型。35~36行测试了TreeMap中不能包含重复的键,如果添加的是重复的键,则更新原键的值。

旧的子类——Hashtable

哈希表(Hashtable,又称散列表)是早期java.util包中的一部分,同时也是Dictionary接口的一个具体实现。然而,自Java 2起,重新设计了哈希表(Hashtable),以便它也能实现映射(Map)接口。因此现在Hashtable也被集成到类集框架中。它与HashMap相似,但它是同步的。和HashMap一样,Hashtable将关键字/值对存储到散列表中。使用Hashtable时,指定一个对象作为关键字,同时指定与该关键字相关联的值。接着,该关键字被散列,而把得到的散列值作为存储在表中的值的下标。

哈希表仅仅可以存储重载由Object定义的hashCode()和equals()方法的对象。hashCode()方法计算和返回对象的散列码。当然,equals()方法比较两个对象。幸运的是,许多Java内置的类已经实现了hashCode()方法。例如,大多数常见的Hashtable类型使用字符串(String)对象作为关键字, String实现hashCode()和equals()方法。

Hashtable的构造方法如下:
在这里插入图片描述
第1种形式是默认的构造方法。第2种形式创建一个哈希表,该散列表具有由size指定的原始大小。第3种形式创建一个哈希表,该哈希表具有由size指定的原始大小和由fillRatio指定的填充比。填充比必须介于0.0和1.0之间,它决定了在散列表向上调整大小之前的充满度。具体地说,当元素的个数大于散列表的容量乘以它的填充比时,哈希表被扩展。如果没有指定填充比,则默认使用0.75。最后,第4种形式创建一个哈希表,该哈希表用m中的元素初始化。哈希表的容量被设为m中元素的个数的两倍,默认的填充因子设为0.75。第4种构造方法是在Java 2中新增加的。

除了Hashtable目前实现的,由Map接口定义的方法之外,Hashtable定义的从以前版本遗留下来的方法列在下表中。
在这里插入图片描述
Hashtable类的使用(HashtableDemo.java)。
在这里插入图片描述
在这里插入图片描述
第06行创建一个Hashtable对象numbers,其中每个哈希元素的关键字(Key)为String类型,值(Value)为Interger类型。第07~09行,利用Hashtable的put()方法添加三个元素。第10行利用get()方法获得哈希表numbers中Key为“two”对应的Value,由于get()方法返回的类型为Object,所以还要用(Integer)做强制类型转换,这是因为第10行左侧的对象n为Integer类型。第13行输出n对应的整数值。

关于Map集合的输出问题

在前面讲解Iterator接口时,我们强调过,若想输出集合的元素,可直接使用Iterator,那么在之前所有的集合都是Collection接口的子类,并且在Collection接口之中也定义了iterator()方法。但可是Map接口中却没有定义iterator()方法,所以现在如果要使用Iterator接口进行Map接口输出的话,就必须首先清楚Collection和Map接口保存对象的形式上的区别。

⑴ Collection中的每一个元素都是一个独立的对象;

⑵ Map中的每一个元素都是Key和Value“结伴而行”的组合对象——也就是所谓的“偶对象”。

下面可通过保存图观察形式上的区别。
在这里插入图片描述
Map.Entry是Map中定义的一个内部接口,而且这个接口是一个使用了static定义的外部接口,在这个接口之中定义了两个非常重要的方法。

⑴ 取得对应的Key的方法。public K getKey();

⑵ 取得对应的Value的方法。public V getValue();

那么清楚了Map.Entry的作用之后,下面就可以采用如下的步骤进行Map的Iterator输出了。

⑴ 通过Map接口之中entrySet()方法将Map集合变为Set集合,Set之中的泛型类型为Map. Entry;

⑵ 利用Set接口之中的iterator()方法取得Iterator接口对象,此时的泛型类型依然为Map.Entry;

⑶ 利用Iterator迭代出每一个Map.Entry对象,再使用getKey()和getValue()方法取出内容。

利用Iterator输出Map接口(iteratorMapDemo.java)。
在这里插入图片描述
在这里插入图片描述
第11行,定义集合set,并通过entrySet()方法将Map集合变为Set集合,Set之中的元素泛型类型为Map.Entry。第12行,利用Set接口之中的iterator()方法取得Iterator接口对象,此时的泛型类型依然为Map.Entry。Iterator的泛型必须和所指向的元素的泛型一致。第13~16行所示的while循环汇总,Iterator迭代出每一个Map.Entry对象,再使用getKey()和getValue()方法分别取出对应的Key和Value。

前期版本遗留下来的类和接口

java.util的最初版本中不包括类集框架。取而代之,它定义了几个类和接口提供专门的方法用于存储对象。随着在Java 2中引入类集,有几种最初的类被重新设计成支持类集接口,因此它们与框架完全兼容。尽管实际上没有类被摈弃,但其中某些方法仍被认为是过时的。当然,在那些重复从以前版本遗留下来的类的功能性的地方,通常都愿意用类集编写新的代码程序。一般来说,对从以前版本遗留下来的类的支持是因为仍然存在着大量使用它们的基本代码,包括现在仍在被Java 2的应用编程接口(API)使用的程序。

另一点,没有一个类集类是同步的。但是所有的从以前版本遗留下来的类都是同步的。这一区别在有些情况下是很重要的。当然,通过使用由Collections提供的算法也很容易实现类集同步。

由java.util定义的从以前版本遗留下来的类如下:Dictionary、Hashtable、Properties、Stack和Vector。下面将介绍部分从以前版本遗留下来的类。

Stack类

Stack是Vector的一个子类,它实现标准的后进先出堆栈。Stack仅仅定义了创建空堆栈的默认构造方法。Stack包括了由Vector定义的所有方法,同时增加了几种它自己定义的方法,具体总结在下表中。
在这里插入图片描述
下面是一个创建堆栈的例子,将几个整型(Integer)对象压入堆栈,然后再将它们弹出。

创建堆栈(StackDemo.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
第02~07行定义了静态方法showpush(),用于演示数据的压栈操作过程。第08~13行定义了静态方法showpop(),用于演示数据的出栈操作。第15行,定义了一个堆栈st。17~19行完成3个数据的压栈,第20~22行完成3个数组的出栈,由于此时堆栈st中没有数据了,第25行再次出栈操作,就会引发EmptyStackException异常。

Dictionary类(了解即可)

字典(Dictionary)是一个表示关键字/值存储库的抽象类,同时它的操作也很像映射(Map)。给定一个关键字和值,可以将值存储到字典(Dictionary)对象中。一旦这个值被存储了,就能够用它的关键字来检索它。因此与映射一样,字典可以被当做关键字/值对列表来考虑。尽管在Java 2中并没有摈弃字典(Dictionary),但由于它已经被映射(Map)所取代,从而被认为是过时的。然而目前Dictionary仍然被广泛地使用,因此这里仍对它进行简要的讨论。

由Dictionary定义的抽象方法如下表所示。
在这里插入图片描述
提示
Dictionary类是过时的。推荐读者使用Map接口去获得关键字/值存储的功能。

属性操作类——Properties类

属性(Properties)是Hashtable的一个子类。但是Properties与Hashtable最大的不同在于:它所能够操作的数据全部是String,Key和Value的类型全部是字符串。Properties类被许多其他的Java类所使用。例如,当获得系统环境值时,System.getProperties( )返回对象的类型。

Properties定义了下面的实例变量。
在这里插入图片描述
这个变量包含了一个与属性(Properties)对象相关联的默认属性列表。Properties定义了如下的构造方法。
在这里插入图片描述
第1种形式创建一个没有默认值的属性(Properties)对象,第2种形式创建一个将propDefault作为其默认值的对象。在这两种情况下,属性列表都是空的。

除了Properties从Hashtable中继承下来的方法之外,Properties自己定义的方法列在下表中。Properties也包含了一个不被赞成使用的方法。save()。它被store()方法所取代,因为它不能正确地处理错误。

在这里插入图片描述
Properties类的一个有用功能是可以指定一个默认属性,如果没有值与特定的关键字相关联,则返回这个默认属性。例如,默认值可以与关键字一起在getProperty()方法中被指定—如getProperty(“name”,“default value”)。如果“name”值没有找到,则返回“defaultvalue”。当构造一个Properties对象时,可以传递Properties的另一个实例作为新实例的默认值。在这种情况下,如果对一个给定的Properties对象调用getProperty(“foo”),而“foo”并不存在时,Java在默认Properties对象中寻找“foo”。它允许默认属性的任意层嵌套。

下面的例子说明了Properties的使用。该程序创建一个属性列表,在其中关键字是各国的名称,值是这些国家的首都。注意试图寻找包括默认值的美国首都时的情况。

Properties的使用(PropertiesDemo.java)
在这里插入图片描述
在这里插入图片描述
由于美国不在列表中,所以使用了默认值。尽管当调用getProperty( )方法时,使用默认值是十分有效的,正如上面的程序所展示的那样,但对大多数属性列表的应用来说,有更好的方法去处理默认值。为了展现更大的灵活性,当构造一个属性(Properties)对象时,可指定一个默认的属性列表。如果在主列表中没有发现期望的关键字,则会搜索默认列表。这种方式只能够操作String型数据,不能够保存对象。

在Properties类中使用store()和load()方法

Properties类的一个最有用的方面是可以利用store()和load()方法方便地对包含在属性(Properties)对象中的信息进行存储或从盘中装入信息。在任何时候,都可以将一个属性(Properties)对象写入流或从中将其读出。这使得属性列表特别便于实现简单的数据库。

在 Properties 类 中 使 用 store() 和 load() 方 法(PropertiesFile.java)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
程序每次启动时都去读取那个记录文件,直接取出文件中所记录的运行次数并加1后,又重新将新的运行次数存回文件。由于第1次运行时硬盘上还没有那个记录文件,程序去读取那个记录文件时会报出一个异常,就在处理异常的语句中将属性的值设置为0,表示程序以前还没有被运行过。如果要用到Properties类的store()方法进行存储,每个属性的关键字和值都必须是字符串类型的,所以上面的程序没有用从父类HashTable继承到的put、get方法进行属性的设置与读取,而是直接用了Properties类的setProperty()、getProperty()方法进行属性的设置与读取。

Collections类

Collections类和Collection接口没有任何的继承关系。它是集合类的一个工具类/辅助类,此类的主要目的是提供了一系列的静态方法,用于对集合中元素进行排序(Sort)、混排(Shuffling)、反转(Reverse)、复制(Copy)以及线程安全等各种操作。

Collections类方法使用范例。
在这里插入图片描述
在这里插入图片描述
第05行创建一个double类型的数组。第06行创建一个ArrayList类型的列表list。在08~10行,在for循环中,使用add()方法用来初始化list。第11行,Collections.sort(list)对list列表中的元素实施从小到大的排序。第17行Collections.reverse(list)对排序后的list列表实施反转。由这两个简单方法的使用上,可以发现,Collections在辅助操作类集元素上还是非常方便的。

类集框架为程序员提供了一个功能强大的设计方案,以完成编程过程中面临的大多数任务。下一次当开发者需要存储和检索信息时,可以考虑使用类集。记住,类集不仅仅是专为那些“大型作业”,例如联合数据库、邮件列表或产品清单系统等所专用的,它们对于一些小型作业也是很有效的。例如, TreeMap可以给出一个很好的类集,以保留一组文件的字典结构。TreeSet在存储工程管理信息时是十分有用的。可以说,对于采用基于类集的解决方案而受益的问题种类,只受限于开发者的想象力。

Java 8中的泛型

从前面知识的学习,我们知道为了区分不同类型的数据并实施不同的操作方式,就有了数据类型的概念。Java的类集框架在本质上就相当于容器,而容器中装的是什么东西需要程序员指定。为了实现了算法与数据类型的无关性以及容器(数据结构)与数据类型的无关性,所以就引出了泛型的概念。

泛型是Java SE 1.5之后引入的新特性,泛型的本质是数据类型参数化,也就是说,所操作的数据类型被指定为一个参数。这种类型变量可以用在类、接口和方法的创建中。

例如,
在这里插入图片描述
这种get方法返回的就是Apple类型,而不是Object类型。

泛型的优点在于提供了容器(数据结构)与数据类型的无关性,并且可以向后兼容,但也有不便的地方,就是每次定义时都要写明泛型的类型,这样显式指定泛型类型不仅感觉有些冗长,最主要的是很多程序员不熟悉泛型,因此有时不能给出正确的类型参数。如果能通过编译器自动推断泛型的参数类型,这样就能够减少这样的情况,并提高代码可读性。

了解新内容

JDK7中添加了尖括号<>操作符,表示是自动类型推断,所以上面的代码在JDK 7中的写法为。
在这里插入图片描述
上面第01行代码中,编译器自动推断等号右边的尖括号中的泛型为Apple。

在Java 8中,进一步强化了泛型的推断能力。Java 8里面泛型的目标类型推断主要有2个:
⑴ 支持通过方法上下文推断泛型目标类型。

⑵ 支持在方法调用链路当中,泛型类型推断传递到最后一个方法。

1. List接口扩展了Collection接口,里面的内容是允许重复的。List接口的常用子类是ArrayList和Vector,在开发中ArrayList性能较高,属于异步处理,而Vector性能较低,属于同步处理。

2. Collection和Collections的区别(面试题)。

Collection是集合操作的接口,而Collections是一个类,专门提供了各个集合接口的操作方法。

3. 请解释两种比较器Comparable和Comparator的区别(面试题)。

如果要进行对象数组的排序那么需要比较器的支持,Java有两种比较器:Comparable、Comparator。

⑴ java.lang.Comparable:只有一个compareTo()方法,是在类定义的时候默认实现好的接口;

⑵ java.util.Comparator:有两个方法compare()、equals(),需要单独编写一个排序规则类。

4. Collection和Map的区别(面试题)。

⑴ Collection之中保存数据的目的是输出;

⑵ Map之中保存数据的目的是为了查找。

Java—给编译器看的注解—Annotation

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值