1、第第7章章 容器和泛型容器和泛型T7.8 泛型方法泛型方法T7.7 泛型泛型T7.12 泛型数组泛型数组T7.1 Collection与与IteratorT7.2 实用类实用类CollectionsT7.3 Set(集集)T7.4 List(列表列表)T7.5 Queue(队列队列)T7.6 Map(映射映射)T7.9 受限泛型受限泛型T7.10 通配符与受限通配符通配符与受限通配符T7.11 子类泛型子类泛型T7.13 综合实例综合实例第第7章章 容器和泛型容器和泛型Java实用类库提供了一套相当完整的容器类来解决这个问题,其中基本的类型是List、Set、Queue和Map,这些对象类型也
2、称为集合类。容器提供了完善的方法来保存对象。Java的容器类库位于java.util包中,Java容器类库中的接口及类之间的关系如图7.1所示,图中实线表示继承,虚线表示接口实现。而泛型的引入最主要原因就是安全地使用容器类。图图7.1 容器类容器类7.1 Collection与与IteratorCollection 是容器类的根接口,List、Set、Queue是它的直接子接口。Collection 表示一组对象,这些对象也称为 Collection 的元素。List类型的容器允许加入重复对象,按照索引位置排序并且按照在容器中的索引位置检索对象。Set类型的容器不允许加入重复对象,也不按照某种
3、方式排序对象。Map接口没有继承Collection接口。Map类型的容器中的每一个元素包含一对键对象和值对象,容器中的键对象不可重复,值对象可以重复。表7.1列出了Collection接口的常用方法。方法描述boolean add(E e)向容器中添加一个元素boolean addAll(Collection c)向容器中添加参数中所有的元素void clear()移除容器中的所有元素boolean contains(Object o)判定此 collection 是否包含指定的元素,有则返回 trueboolean containsAll(Collection c)判定此 collecti
4、on 是否包含指定 collection 中的所有元素,是则返回 trueboolean isEmpty()判定此容器是否为空,是则返回trueIterator iterator()返回一个Iterator,用来遍历容器中的所有元素boolean remove(Object o)如果容器中存在此元素,则删除它boolean retainAll(Collection c)将此Collection与参数c的交集存入此Collection中int size()返回此 collection 中的元素数目Object toArray()返回包含此 collection 中所有元素的数组表表7.1 Col
5、lection的常用方法的常用方法7.1 Collection与与IteratorCollection接口的iterator()和toArray()方法都用于获得容器中所有元素,前者返回一个Iterator对象,后者返回一个包含容器中所有元素的数组。Iterator接口中声明了如下方法:lboolean hasNext():判断容器中的元素是否遍历完毕,没有则返回true。lnext():返回迭代的下一个元素。lvoid remove():从迭代器指向的Collection中移除迭代器返回的最后一个元素。必须先调用一次next()方法之后,才能调用一次remove(),即remove()不能连
6、续多次调用。【例例7.1】向容器中添加一组元素,用iterator()方法遍历容器中的元素。7.2 实用类实用类Collections就像java.util.Arrays一样,java.util.Collections类也有一些实用的static方法,其中一部分方法专门用于操纵List类型容器,还有一部分方法可用于操纵所有的Collection类型或Map类型容器。List代表长度可变的线性表,Collections的以下方法适用于List类型。copy(List dest,List src):将所有元素从一个列表复制到另一个列表。fill(List list,T obj):使用指定元素替换指
7、定列表中的所有元素。nCopies(int n,T o):返回由指定对象的n个副本组成的不可修改的列表。shuffle(List list):使用默认随机源对指定列表进行置换。sort(List list):根据元素的自然顺序对指定列表按升序进行排序。【例例7.2】使用Collections的nCopies()、min()、max()、binarySearch()等常用方法。7.3 Set(集集)7.3.1 HashSetHashSet类按照哈希算法来存取容器中的对象,具有很好的存取和查找性能。当向容器中加入一个对象时,HashSet会调用对象的hashCode()方法来获取哈希码,然后根据这
8、个哈希码进一步计算出对象在容器中的存放位置。在Object类中定义hashCode()和equals()方法,Object类的equals()方法按照对象的内存地址比较对象是否相等,因此如果object1.equals(object2)为true,则表明object1变量和object2变量实际上引用同一个对象,那么object1和object2的哈希码也肯定相同。为了保证HashSet能正常工作,要求当两个对象用 equals()方法比较的结果为true时,它们的哈希码也相等。例如,如果object1.equals(object2)为true 时,那么以下表达式的结果也应为true。obje
9、ct1.hashCode()=object2.hashCode();如果用户定义的类覆盖Object类equals()方法,但没有覆盖Objec类hashCode()方法就会导致object1.equals(object2)为true 时,而object1和object2的哈希码不一定一样,这会使HashSet无法正常工作。【例例7.3】测试不同时重载Object类的equals()和hashCode()方法。7.3.2 TreeSet TreeSet类实现了SortedSet接口,能够对容器中的对象进行排序。当向TreeSet中加入一个对象后,会继续保持对象间的排序的次序,例如下面的代码片段
10、:Set set=new TreeSet();set.add(new String(spring);set.add(new String(summer);set.add(new String(autumn);set.add(new String(winter);System.out.println(set);运行结果:autumn,spring,summer,winterTreeSet支持两种排序方式:自然排序和指定排序。在默认的情况下TreeSet采用自然排序方式。h1.自然排序自然排序在JDK类库中,有一部分类实现了java.lang.Comparable接口,如Integer、Doubl
11、e 和String等。Comparable接口有一个compareTo(Object o)方法,它返回整数类型。pareTo(y):如果返回值为0,则表示x 和y 相等;如果返回值大于0,则表示x大于y;如果返回值小于0,则表示x小于y。7.3.2 TreeSet TreeSet调用对象的compareTo()方法比较容器中对象的大小,然后进行升序排列。表7.2列出了JDK类库中实现了Comparable接口的一些类的排序及其排序方式。类排序方式BigDecimal、Integer、Byte、DoubleFloat、Integer、Long、Short按数字大小排序Character按字符的U
12、nicode值的数字大小排序String 按字符串字符的Unicode值排序表表7.2 类的排序类的排序使用自然排序时,只能向TreeSet容器中加入同类型的对象,如下所示。Set set=new TreeSet();set.add(new Integer(1);set.add(new String(spring);System.out.println(set);/抛出java.lang.ClassCastException异常向TreeSet容器中加入同类型的对象,要求这些对象的类必须实现Comparable接口。【例例7.4】向TreeSet容器中加入4个雇员信息,并按工资的多少进行升序排
13、列。7.3.2 TreeSet h2.指定排序指定排序Java.util.Comparator接口提供具体的排序方式,指定被比较的对象的类型,Comparator接口的compare(T o1,T o2)方法用于比较两个对象的大小。当compare(T o1,T o2)的返回值大于0时,表示o1大于o2;当compare(T o1,T o2)的返回值等于0时,表示o1等于o2;当compare(T o1,T o2)的返回值小于0时,表示o1小于o2。【例例7.5】实现Comparator接口,加入TreeSet容器中的对象以brand降序排列、以place升序排列。7.4 List(列表列表)
14、7.4.1 ArrayListArrayList代表长度可变的数组,允许对元素进行随机的快速访问,但是向ArrayList中插入与删除元素的速度较慢。ArrayList是线程不安全的,若要成为线程安全的,可用:List list=Collections.synchronizedList(new ArrayList();【例例7.6】运用ArrayList类的各种方法,并展示相似方法的异同点。说明:说明:该程序用到了ArrayList的多个方法。创建了一个ArrayList类型的容器,通过add方法添加四个String类型的对象。容器中含有horse对象,contains()方法返回true。使
15、用remove()方法移除用get()方法得到的pig对象。用subList()方法得到从fromIndex(包括)和 toIndex(不包括)之间的部分视图。Collections.sort(sub)语句对容器中的对象按字母进行排序。Collections.shuffle(sub,rand)语句使用指定的随机源对指定列表进行置换。retainAll()方法是一种有效的“交集”操作,在本例中,它保留了所有同时在copy与sub中的元素。将指定 Collection 中的所有元素插入列表中的指定位置。执行list.toArray()语句返回一个数组,该数组包含容器中的所有元素。执行list.to
16、Array(new String0 语句返回一个数组,该数组包含容器中的所有元素,返回数组的运行时类型与参数数组的类型完全相同,而不是单纯的Object。7.4.2 LinkedListLinkedList在内部是采用双向循环链表实现的,插入与删除元素的速度较快,随机访问速度则较慢。LinkedList单独具有addFirst()、addLast()、getFirst()、getLast()、removeFirst()和removeLast()方法,这些方法使得LinkedList可以作为堆栈、队列和双向队列来使用。这些方法彼此之间只是名称有些差异,或者只存在较少差异,以使得这些名字在特定用法
17、的上下文环境中更加适用(特别是在Queue中)。同样,LinkedList也是线程不安全的。【例例7.7】测试LinkedList类的一些方法的异同点。7.4.3 栈的实现栈的实现“栈”通常是后进先出的容器,先进的后弹出。LinkedList具有能够直接实现栈功能的方法,因此可以直接将LinkedList作为栈使用。【例例7.8】用LinkedList实现栈的功能。7.5 Queue(队列队列)队列是一个典型的先进先出的容器。即从容器的一端放入对象,从另一端取出,并且对象放入容器的顺序与取出的顺序是相同的。队列常被当作一种可靠的将对象从程序的某个区域传输到另一个区域的途径。LinkedList
18、提供了方法以支持队列行为,并且它实现了Queue接口,因此LinkedList可以用作Queue的一种实现。通过将LinkedList向上转型为Queue。【例例7.9】使用LinkedList实现队列。7.5.1 PriorityQueueQueue的实现类是PriorityQueue,在PriorityQueue上调用offer()方法来插入一个对象时,这个对象会在队列中被排序。默认的排序将使用对象在队列中的自然顺序,但是可以通过提供Comparator来修改这个顺序。PriorityQueue可以确保当调用peek()、poll()和remove()方法时,获取的元素将是队列中的优先级最
19、高的元素。优先级队列不允许null元素,另外优先级队列不是线程安全的,若要线程安全的,可用java.util.concurrent.PriorityBlockingQueue。【例例7.10】使用PriorityQueue获取优先级最高的元素。7.5.2 实现队列实现队列在前面讲述List时,使用LinkedList实现了栈的功能,用LinkedList同样可以实现队列的功能。如:保持在尾部加入元素,在首部取走元素,或者反之。【例例7.11】使用LinkedList实现队列的功能。7.5.3 双向队列双向队列双向队列(或双端队列)就像是一个队列,可以在任何一端添加或移去元素,在LinkedLi
20、st中包含支持双向队列的方法。【例例7.12】在双向队列的两端分别加入元素,并分别在队列的两端删除元素。7.6 Map(映射映射)Map(映射)是一种把键对象和值对象进行映射的集合,它的每一个元素都包含一对键对象和值对象,而值对象仍可以是Map类型,依次类推,这样就形成了多级映射。向Map容器中加入元素时,必须提供一对键对象和值对象。从Map容器中检索元素时,只要给出键对象,就会返回对应的值对象。在Map中,每个键最多只能映射一个值。【例例7.13】使用Map的put()方法向容器中加入元素,使用get()方法来检索与键对象对应的值对象。7.6.1 HashMapHashMap的实例有两个参数
21、影响其性能:初始容量和加载因子。容量是哈希表中桶的数量,初始容量只是哈希表在创建时的容量。加载因子是哈希表在其容量自动增加之前可以达到多满的一种尺度。当哈希表中的条目数超出了加载因子与当前容量的乘积时,则要对该哈希表进行rehash操作(即重建内部数据结构),从而哈希表将具有大约两倍的桶数。通常,默认加载因子(.75)在时间和空间成本上寻求一种折衷。加载因子过高虽然减少了空间开销,但同时也增加了查询成本。在设置初始容量时应该考虑到映射中所需的条目数及其加载因子,以便最大限度地减少rehash操作次数。如果初始容量大于最大条目数除以加载因子,则不会发生rehash操作。如果很多映射关系要存储在H
22、ashMap实例中,则相对于按需执行自动rehash操作以增大表的容量来说,使用足够大的初始容量创建它将使得映射关系能更有效地存储。HashMap是基于HashCode的,若想正确使用HashMap,则需重写hashCode()和equals()方法。HashMap不是线程安全的,若要线程安全,可用:Map m=Collections.synchronizedMap(new HashMap();【例例7.14】设计一个Java程序,统计任意给定的一个字符串中,每一个英文字母的使用频度。7.6.2 TreeMap使用SortedMap接口,可以确保键处于排序状态。这些功能的实现由SortedMa
23、p接口的方法提供。l Comparator compartor():返回当前Map使用的Compartor;或者返回null,表示以自然方式排序。lfirstKey():返回Map中的第一个键。llastKey():返回Map中的最后一个键。l SortedMap subMap(fromKey,toKey):生成此Map的子集,由键小于toKey的所有键值对组成。l SortedMap tailMap(fromKey):生成此Map的子集,由键大于或等于toKey的所有键值对组成。TreeMap类是接口SortedMap的唯一实现,并且是基于红黑树的。该映射根据其键的自然顺序进行排序,或者根据
24、创建映射时提供的Comparator进行排序,具体取决于使用的构造方法。TreeMap是唯一带有subMap()方法的Map,它可以返回一个子树。【例例7.15】设计一个Java程序,所要完成的功能:对一个由数字和非数字组成的字符串,将其中连续的数字字符转换成一个整数。若连续的数字字符个数超过4个,则以4个数字字符为一组进行转换。转换生成的整数依次存放在整数数组中。7.7 泛型泛型Java SE5引入了“泛型”的概念。泛型实现了参数化类型的概念,使代码可以应用于多种类型。泛型这个术语的意思是“适用于许多种的类型”,目的是希望类或方法能够具备最广泛的表达能力。【例例7.16】指定其持有Objec
25、t类型的对象。说明:说明:BasicType类可以存储任何类型的对象,但要想得到存储的对象,必须强制转换到正确的类型。例如,当试图把存储的String对象强制转换为Double类型时,编译器不会检查到错误,但在运行时会抛出ClassCastException异常。编译期能检查出的错误就不应该等到运行时才被发现。Java泛型机制正好可以解决此类问题,它要求编译期的严格类型检查。【例例7.17】使用泛型类指定其持有的对象的类型。在程序BasicGeneric.java中,T是类型参数,所以在创建BasicGeneric对象时,必须指明想持有什么类型的对象,将其置于尖括号内,例如下面的语句:Basi
26、cGeneric generic1 =new BasicGeneric(new B();7.7 泛型泛型然后,就只能在generic1中存入该类型的对象了,这时的T类型就是引用类型B,所以通过get()方法就能自动地获得正确的类型,而无需强制类型转换。在创建generic2对象时,T又变成Double类型。其实,一个泛型类就是具有一个或多个类型变量的类。即泛型类可以带有两个及以上类型参数,参数之间用逗号分隔。【例例7.18】带有两个类型参数T1、T2的泛型类。7.8 泛型方法泛型方法前面介绍的都是如何定义一个泛型类,其实还可以定义一个泛型方法。要定义泛型方法,只需将泛型参数列表置于返回值之前。
27、【例例7.19】在GenericMethods类中定义泛型方法f()。可变参数是JDK 5.0新增加的特性,可变参数可用于泛型方法,表示声明一个接受可变数目参数的泛型方法。注意,可变参数必须是方法声明中的最后一个参数,【例例7.20】在泛型类中使用可变参数。7.9 受限泛型受限泛型使用extends关键字指定这个类型必须是继承某个类或者实现某个接口,并且可以用这些类型子集来调用方法。当然,可以显式设定参数类型的范围,确保没有用不适当的类型来实例化类型参数。为什么要在类型参数上设定范围呢?首先,范围增加了静态类型检查功能。有了静态类型检查,就能保证泛型类型的每次实例化都符合所设定的范围。其次,因
28、为知道类型参数的每次实例化都是这个范围之内的子类,所以可以放心地调用类型参数实例出现在这个范围之内的任何方法。【例例7.21】使用extends关键字限制泛型的可用参数类型。7.10 通配符与受限通配符通配符与受限通配符考虑下面的一个简单的泛型类,在这个泛型类中只有简单的setXXX()和getXXX()方法。public class Generics private T obj;public void setObj(T obj)this.obj=obj;public T getObj()return obj;可以给上面的这个泛型类定义两个引用:Generics gen1=null;Gener
29、ics gen2=null;7.10 通配符与受限通配符通配符与受限通配符那么gen1就只接受Generics的对象,而gen2只接受Generics的对象。现在有一个需求,希望有一个名称为gen的引用可以接受所有下面的对象。gen=new Generics();gen=new Generics();简单的说,参数化类型必须是List类型或其子类型,要满足这种要求,可以使用“?”通配符,并使用“extends”关键字限定参数化类型,例如下面的语句:Generics gen=null;gen=new Generics();gen=new Generics();如果指定的不是List的类型或者子类
30、型,则编译器会报告错误,例如下面的语句:Generics gen=new Generics();7.10 通配符与受限通配符通配符与受限通配符如果只指定了而不使用“extends”关键字,则可以是Object类及其子类,也就是所有的类了。那么为什么不直接使用Generics呢?何必要用Generics。通过使用通配符,可限制对它加入新的信息,只能获取它的信息或是移除它的信息,例如下面的语句:Generics gen=new Generics();gen.setObj(cat);Generics gen2=gen;System.out.println(gen2.getObj();/可以获取信息g
31、en2.setObj(null);/可通过gen2来移去gen的信息gen2.setObj(dog);/不可通过gen2来设定新的信息给gen要限定父类的类型时,可以使用“?”通配符,并使用“super”关键字。例如下面的语句:Generics ge=null;ge=new Generics();【例例7.22】通配符与受限通配符的使用。7.11 子类泛型子类泛型泛型和普通类一样,子类泛型可以继承父类泛型,子类泛型还可以实现父类泛型接口。例如下面的语句块。【例例7.23】子类泛型继承父类泛型。7.12 泛型数组泛型数组Java并不能创建泛型数组。一种解决方法是在需要创建数组的地方使用Array
32、List。例如下面的代码:public class Genericsprivate List arr=new ArrayList();public void add(T item)arr.add(item);public T get(int index)return arr.get(index);7.12 泛型数组泛型数组Generic类获得数组的行为以及由泛型提供的编译期的类型安全。但有时,仍旧希望创建泛型类型的数组(例如,ArrayList内部使用的数组),可以定义一个引用,例如下面的语句:class Genericpublic class ArrayReferencestatic Gen
33、eric ga;编译器将接受这个程序,而不会产生任何警告。但是不能创建这个确切类型的数组(包括类型参数)。数组的运行时类型只能是Object,如果立即将其转型为某种类型T,那么在编译期该数组的实际类型就将丢失。【例例7.24】测试泛型数组。7.13 综合实例综合实例【例例7.25】向容器中查询某一学生信息,如果存在则将该学生信息打印出来并对容器中的5个学生信息依总学分的升序打印出来。思路思路:由于TreeMap容器中的值对象是Student,需要一一取出它们,计算总学分,再以总学分作为键对象放入到TreeMap容器中,值对象依然以Student。TreeMap容器会自动以自然顺序对键值对进行排
34、序。7.13 综合实例综合实例右击“MapTest.java”,选择“Run As”“Run Configurations”,如图7.2所示,选择Main标签页,在“Project”栏中选择“MyProject_07”,在“Main class”栏中选择“MapTest”,选择“Arguments”标签页,在“Program argumentds”栏中输入“100003”,然后单击“Run”按钮,运行程序。图图7.2 查询学生信息查询学生信息7.13 综合实例综合实例程序运行结果:学号 姓名 计算机成绩 数学成绩 英语成绩 总学分你要查找的学生信息是:100003 严红 80 80 92 252按总学分排序前:100001 王军 95 75 85 255100002 李计 80 70 90 240100003 严红 80 80 92 252100004 马莉 76 87 80 243100005 刘燕 60 70 80 210按总学分排序后:100005 刘燕 60 70 80 210100002 李计 80 70 90 240100004 马莉 76 87 80 243100003 严红 80 80 92 252100001 王军 95 75 85 255