Java 集合排序及 java 集合类详解

Java 集合排序及 java 集合类详解
(Collection, List, Set, Map)
摘要内容
Java 里面最重要,最常用也就是集合一部分了。能够用好集合和理
解好集合对于做 Java 程序的开发拥有无比的好处。本文详细解释了关
于 Java 中的集合是如何实现的,以及他们的实现原理。
关键字:
Collection , List ,Set , Map , 集合,框架。
目 录
1 集合框架 ........................................................................................................................2
1.1 集合框架概述....................................................................................................2
1.1.1 容器简介 .................................................................................................2
1.1.2 容器的分类 ...............................................................................................4
1.2 Collection............................................................................................................6
1.2.1 常用方法 .................................................................................................6
1.2.2 迭代器 ......................................................................................................8
1.3 List......................................................................................................................10
1.3.1 概述 ...........................................................................................................10
1.3.2 常用方法 ...............................................................................................11
1.3.3 实现原理 ...............................................................................................15
1.4 Map .....................................................................................................................18
1.4.1 概述 ...........................................................................................................18
1.4.2 常用方法 ...............................................................................................18
1.4.3 Comparable 接口 ................................................................................23
1.4.4 实现原理 ...............................................................................................25
1.4.5 覆写 hashCode()....................................................................................29
1.5 Set..........................................................................................................................33
1.5.1 概述 ...........................................................................................................33
1.5.2 常用方法 ..................................................................................................34
1.5.3 实现原理 ...............................................................................................38
1.6 总结:集合框架中常用类比较 ........................................................................39
2 练习 ..................................................................................................................................40
3 附录:排序.....................................................................................................................41
青菜制作 qq :83395110
12/7/2009 5:17 PM
1 集合框架
1.1 集合框架概述
1.1.1 容器简介
到目前为止,我们已经学习了如何创建多个不同的对象,定义了这
些对象以后,我们就可以利用它们来做一些有意义的事情。
举例来说,假设要存储许多雇员,不同的雇员的区别仅在于雇员
的身份证号。我们可以通过身份证号来顺序存储每个雇员,但是在内存
中实现呢?是不是要准备足够的内存来存储 1000 个雇员,然后再将这
些雇员逐一插入?如果已经插入了 500 条记录,这时需要插入一个身份
证号较低的新雇员, 该怎么办呢?是在内存中将 500 条记录全部下移后,
再从开头插入新的记录? 还是创建一个映射来记住每个对象的位置?
当决定如何存储对象的集合时,必须考虑如下问题。
对于对象集合,必须执行的操作主要以下三种:
? 添加新的对象
? 删除对象
? 查找对象
我们必须确定如何将新的对象添加到集合中。可以将对象添加到集
合的末尾、开头或者中间的某个逻辑位置。
从集合中删除一个对象后,对象集合中现有对象会有什么影响呢?
可能必须将内存移来移去,或者就在现有对象所驻留的内存位置下一个
擌洞敶。
在内存中建立对象集合后,必须确定如何定位特定对象。可建立
一种机制,利用该机制可根据某些搜索条件(例如身份证号)直接定位
到目标对象;否则,便需要遍历集合中的每个对象,直到找到要查找的
对象为止。
前面大家已经学习过了数组。数组的作用是可以存取一组数据。
但是它却存在一些缺点,使得无法使用它来比较方便快捷的完成上述应
用场景的要求。
1. 首先,在很多数情况下面,我们需要能够存储一组数据的容
器,这一点虽然数组可以实现,但是如果我们需要存储的数据
青菜制作 qq :83395110
12/7/2009 5:17 PM
的个数多少并不确定。比如说:我们需要在容器里面存储某个
应用系统的当前的所有的在线用户信息, 而当前的在线用户信
息是时刻都可能在变化的。 也就是说,我们需要一种存储数
据的容器, 它能够自动的改变这个容器的所能存放的数据数量
的大小。这一点上,如果使用数组来存储的话,就显得十分的
笨拙。
2. 我们再假设这样一种场景:假定一个购物网站,经过一段时
间的运行,我们已经存储了一系列的购物清单了,购物清单中
有商品信息。如果我们想要知道这段时间里面有多少种商品被
销售出去了。那么我们就需要一个容器能够自动的过滤掉购物
清单中的关于商品的重复信息。如果使用数组,这也是很难实
现的。
3. 最后再想想,我们经常会遇到这种情况,我知道某个人的帐
号名称,希望能够进一步了解这个人的其他的一些信息。也就
是说,我们在一个地方存放一些用户信息,我们希望能够通过
用户的帐号来查找到对应的该用户的其他的一些信息。 再举个
查字典例子:假设我们希望使用一个容器来存放单词以及对于
这个单词的解释,而当我们想要查找某个单词的意思的时候,
能够根据提供的单词在这个容器中找到对应的单词的解释。 如
果使用数组来实现的话,就更加的困难了。
为解决这些问题,Java 里面就设计了容器集合,不同的容器集合以
不同的格式保存对象。
数学背景
在常见用法中,集合(collection)和数学上直观的集(set)的概
念是相同的。集是一个唯一项组,也就是说组中没有重复项。实际上,
“集合框架”包含了一个 Set 接口和许多具体的 Set 类。但正式的集
概念却比 Java 技术提前了一个世纪,那时英国数学家 George Boo
le 按逻辑正式的定义了集的概念。大部分人在小学时通过我们熟悉的
维恩图引入的“集的交”和“集的并”学到过一些集的理论。
青菜制作 qq :83395110
12/7/2009 5:17 PM
集的基本属性如下:
? 集内只包含每项的一个实例
? 集可以是有限的,也可以是无限的
? 可以定义抽象概念
集不仅是逻辑学、数学和计算机科学的基础,对于商业和系统的日
常应用来说,它也很实用。“连接池”这一概念就是数据库服务器的一个
开放连接集。Web 服务器必须管理客户机和连接集。文件描述符提供
了操作系统中另一个集的示例。
映射是一种特别的集。它是一种对(pair)集,每个对表示一个元
素到另一元素的单向映射。一些映射示例有:
? IP 地址到域名(DNS)的映射
? 关键字到数据库记录的映射
? 字典(词到含义的映射)
? 2 进制到 10 进制转换的映射
就像集一样,映射背后的思想比 Java 编程语言早的多,甚至比计
算机科学还早。而 Java 中的 Map 就是映射的一种表现形式。
1.1.2 容器的分类
既然您已经具备了一些集的理论,您应该能够更轻松的理解“集合框
架”。 “集合框架”由一组用来操作对象的接口组成。不同接口描述不同
类型的组。在很大程度上,一旦您理解了接口,您就理解了框架。虽然
您总要创建接口特定的实现,但访问实际集合的方法应该限制在接口方
法的使用上;因此,允许您更改基本的数据结构而不必改变其它代码。
框架接口层次结构如下图所示。
Java 容器类类库的用途是撉保存对象旙,并将其划分为两个不同的
概念:
1) Collection 。 一组对立的元素,通常这些元素都服从某种规则。
List 必须保持元素特定的顺序,而Set 不能有重复元素。
青菜制作 qq :83395110
12/7/2009 5:17 PM
2) Map 。 一组 成对的撃键值对斣对象。初看起来这似乎应该是
一个 Collection ,其元素是成对的对象,但是这样的设计实现起
来太笨拙了,于是我们将 Map 明确的提取出来形成一个独立的概
念。另一方面,如果使用 Collection 表示 Map 的部分内容,会便
于查看此部分内容。因此 Map 一样容易扩展成多维 Map ,无需
增加新的概念,只要让 Map 中的键值对的每个擌值數也是一个 M
ap 即可。
Collection 和 Map 的区别在于容器中每个位置保存的元素个数。Co
llection 每个位置只能保存一个元素(对象)。此类容器包括:List ,
它以特定的顺序保存一组元素;Set 则是元素不能重复。
Map 保存的是 撉键值对斣 , 就像一个小型数据库。 我们可以通过 擙键旤
找到该键对应的撃值數。
? Collection 枺 对象之间没有指定的顺序,允许重复元素。
? Set 枺 对象之间没有指定的顺序,不允许重复元素
? List栘 对象之间有指定的顺序,允许重复元素,并引入位置
下标。
? Map 枺 接口用于保存关键字(Key)和数值(Value)的集
合,集合中的每个对象加入时都提供数值和关键字。Map 接口
既不继承 Set 也不继承 Collection。
List、Set、Map 共同的实现基础是 Object 数组
除了四个历史集合类外,Java 2 框架还引入了六个集合实现,如
下表所示。
接口 实现 历史集合类
Set HashSet
TreeSet
List ArrayList Vector
LinkedList Stack
Map HashMap Hashtable
TreeMap Properties
这里没有 Collection 接口的实现,接下来我们再来看一下下面的
这张关于集合框架的大图:
青菜制作 qq :83395110
12/7/2009 5:17 PM
这张图看起来有点吓人,熟悉之后就会发现其实只有三种容器:
Map,List和 Set ,它们各自有两个三个实现版本。常用的容器用黑色
粗线框表示。
点线方框代表擁接口斱,虚线方框代表抽象类,而实线方框代表普
通类(即具体类,而非抽象类)。虚线箭头指出一个特定的类实现了一
个接口(在抽象类的情况下,则是撉部分斨实现了那个接口)。实线箭
头指出一个类可生成箭头指向的那个类的对象。例如任何集合( Colle
ction )都能产生一个迭代器( Iterator ), 而一个 List 除了能生成一
个 ListIterator (列表迭代器)外,还能生成一个普通迭代器,因为 List
正是从集合继承来的.
1.2 Collection
1.2.1 常用方法
Collection 接口用于表示任何对象或元素组。想要尽可能以常规方式
处理一组元素时,就使用这一接口。Collection 在前面的大图也可以看出,它是
List 和 Set 的父类。并且它本身也是一个接口。它定义了作为集合所应该拥有的
一些方法。如下:
注意:
集合必须只有对象,集合中的元素不能是基本数据类型。
Collection 接口支持如添加和除去等基本操作。设法除去一个元素时,如
果这个元素存在,除去的仅仅是集合中此元素的一个实例。
? boolean add(Object element)
? boolean remove(Object element)
Collection 接口还支持查询操作:
? int size()
? boolean isEmpty()
? boolean contains(Object element)
? Iterator iterator()
组操作 :Collection 接口支持的其它操作,要么是作用于元素组的任务,
要么是同时作用于整个集合的任务。
? boolean containsAll(Collection collection)
? boolean addAll(Collection collection)
青菜制作 qq :83395110
12/7/2009 5:17 PM
? voidclear()
? voidremoveAll(Collection collection)
? voidretainAll(Collection collection)
containsAll() 方法允许您查找当前集合是否包含了另一个集合的所有元素, 即另一个集合是
否是当前集合的子集。其余方法是可选的,因为特定的集合可能不支持集合更改。 addAll()
方法确保另一个集合中的所有元素都被添加到当前的集合中,通常称为并。 clear() 方法从
当前集合中除去所有元素。removeAll()方法类似于 clear() , 但只除去了元素的一个子集。
retainAll() 方法类似于 removeAll() 方法,不过可能感到它所做的与前面正好相反:它从当
前集合中除去不属于另一个集合的元素,即交。
我们看一个简单的例子,来了解一下集合类的基本方法的使用:
import java.util.*;
public class CollectionToArray {
public static void main(String[] args) {
Collection collection1=newArrayList();//创建一个集合对象
collection1.add("000");//添加对象到 Collection 集合中
collection1.add("111");
collection1.add("222");
System.out.println("集合 collection1 的大小:"+collection1.size());
System.out.println("集合 collection1 的内容:"+collection1);
collection1.remove("000");//从集合 collection1 中移除掉 "000" 这个对象
System.out.println("集合 collection1 移除 000 后的内容:"+collection1);
System.out.println("集合 collection1 中是否包含 000 : "+collection1.contains("000"));
System.out.println("集合 collection1 中是否包含 111: "+collection1.contains("111"));
Collection collection2=newArrayList();
collection2.addAll(collection1);//将 collection1 集合中的元素全部都加到collection2

System.out.println("集合 collection2 的内容:"+collection2);
collection2.clear();//清空集合 collection1 中的元素
System.out.println("集合 collection2 是否为空 :"+collection2.isEmpty());
//将集合 collection1 转化为数组
Object s[]= collection1.toArray();
for(int i=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
运行结果为:
集合collection1 的大小:3
集合collection1 的内容:[000, 111, 222]
集合collection1 移除 000 后的内容:[111, 222]
集合collection1 中是否包含 000 :false
集合collection1 中是否包含 111 :true
青菜制作 qq :83395110
12/7/2009 5:17 PM
集合collection2 的内容:[111, 222]
集合collection2 是否为空 :true
111
222
这里需要注意的是,Collection 它仅仅只是一个接口,而我们真正
使用的时候,确是创建该接口的一个实现类。做为集合的接口,它定义
了所有属于集合的类所都应该具有的一些方法。
而 ArrayList(列表)类是集合类的一种实现方式。
这里需要一提的是,因为 Collection的实现基础是数组,所以有转
换为 Object 数组的方法:
? Object[] toArray()
? Object[] toArray(Object[] a)
其中第二个方法 Object[] toArray(Object[] a) 的参数 a 应该是集
合中所有存放的对象的类的父类。
1.2.2 迭代器
任何容器类,都必须有某种方式可以将东西放进去,然后由某种方
式将东西取出来。毕竟,存放事物是容器最基本的工作。对于 ArrayLis
t,add()是插入对象的方法,而 get()是取出元素的方式之一。ArrayL
ist 很灵活,可以随时选取任意的元素,或使用不同的下标一次选取多个
元素。
如果从更高层的角度思考,会发现这里有一个缺点:要使用容器,
必须知道其中元素的确切类型。初看起来这没有什么不好的,但是考虑
如下情况:如果原本是 ArrayList ,但是后来考虑到容器的特点,你想
换用 Set , 应该怎么做?或者你打算写通用的代码, 它们只是使用容器,
不知道或者说不关心容器的类型,那么如何才能不重写代码就可以应用
于不同类型的容器?
所以迭代器(Iterator)的概念,也是出于一种设计模式就是为达成
此目的而形成的。所以 Collection 不提供 get()方法。如果要遍历 Collec
tin 中的元素,就必须用 Iterator。
青菜制作 qq :83395110
12/7/2009 5:17 PM
迭代器(Iterator)本身就是一个对象,它的工作就是遍历并选择
集合序列中的对象,而客户端的程序员不必知道或关心该序列底层的结
构。此外,迭代器通常被称为摢轻量级敹对象,创建它的代价小。但是,
它也有一些限制,例如,某些迭代器只能单向移动。
Collection 接口的 iterator() 方法返回一个 Iterator 。 Iterat
or 和您可能已经熟悉的 Enumeration 接口类似。使用 Iterator 接
口方法,您可以从头至尾遍历集合,并安全的从底层 Collection 中
除去元素。
下面,我们看一个对于迭代器的简单使用:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class IteratorDemo {
public static void main(String[]args) {
Collection collection = new ArrayList();
collection.add("s1");
collection.add("s2");
collection.add("s3");
Iterator iterator = collection.iterator();//得到一个
迭代器
while (iterator.hasNext()) {//遍历
Object element = iterator.next();
System.out.println("iterator = " + element);
}
if(collection.isEmpty())
System.out.println("collection is Empty!");
else
System.out.println("collection is not Empty!size
="+collection.size());
Iterator iterator2 = collection.iterator();
while (iterator2.hasNext()) {//移除元素
Object element = iterator2.next();
System.out.println("remove:"+element);
iterator2.remove();
}
Iterator iterator3 = collection.iterator();
if (!iterator3.hasNext()) {//察看是否还有元素
System.out.println("还有元素");
}
if(collection.isEmpty())
青菜制作 qq :83395110
12/7/2009 5:17 PM
System.out.println("collection is Empty!");
//使用 collection.isEmpty()方法来判断
}
}
程序的运行结果为:
iterator = s1
iterator = s2
iterator = s3
collection is not Empty!size=3
remove:s1
remove:s2
remove:s3
还有元素
collection is Empty!
可以看到,Java 的 Collection 的 Iterator 能够用来,:
1) 使用方法 iterator() 要求容器返回一个 Iterator .第一次调
用 Iterator 的 next() 方法时,它返回集合序列的第一个元素。
2) 使用 next() 获得集合序列的中的下一个元素。
3) 使用 hasNext()检查序列中是否元素。
4) 使用 remove()将迭代器新返回的元素删除。
需要注意的是:方法删除由 next 方法返回的最后一个元素,在每次
调用 next 时,remove 方法只能被调用一次 。
大家看,Java 实现的这个迭代器的使用就是如此的简单。Iterator
(跌代器)虽然功能简单,但仍然可以帮助我们解决许多问题,同时针
对 List 还有一个更复杂更高级的 ListIterator。您可以在下面的 List 讲
解中得到进一步的介绍。
1.3 List
1.3.1 概述
前面我们讲述的 Collection 接口实际上并没有直接的实现类。而 List 是
容器的一种,表示列表的意思。当我们不知道存储的数据有多少的情况,我
们就可以使用 List来完成存储数据的工作。例如前面提到的一种场景。我
们想要在保存一个应用系统当前的在线用户的信息。我们就可以使用一个 Li
st 来存储。因为 List 的最大的特点就是能够自动的根据插入的数据量来动态
青菜制作 qq :83395110
12/7/2009 5:17 PM
改变容器的大小。下面我们先看看 List 接口的一些常用方法。
1.3.2 常用方法
List 就是列表的意思,它是 Collection 的一种, 即 继承了 Collection
接口,以定义一个允许重复项的有序集合。该接口不但能够对列表的一部分进行
处理,还添加了面向位置的操作。List 是按对象的进入顺序进行保存对象,而
不做排序或编辑操作。它除了拥有 Collection 接口的所有的方法外还拥有一些其
他的方法。
面向位置的操作包括插入某个元素或 Collection 的功能,还包括获取、
除去或更改元素的功能。在 List 中搜索元素可以从列表的头部或尾部开始,
如果找到元素,还将报告元素所在的位置。
? void add(int index,Object element) :添加对象 element 到位置 inde
x 上
? boolean addAll(int index, Collection collection) :在 index 位置后添
加容器 collection 中所有的元素
? Object get(int index) :取出下标为 index 的位置的元素
? int indexOf(Object element) :查找对象 element 在 List 中第一次出
现的位置
? int lastIndexOf(Object element) :查找对象element 在 List 中最后出
现的位置
? Object remove(int index) :删除 index 位置上的元素
? Object set(int index, Object element) : 将 index 位置上的对象替换为
element 并返回老的元素。
先看一下下面表格:
简述 实现 操作特性 成员要求
ArrayList
提供快速的基于索引的成员访
问,对尾部成员的增加和删除
支持较好
成员可为任意
Object 子类的对象
List
提供基于索引
的对成员的随
机访问
LinkedList
对列表中任何位置的成员的增
加和删除支持较好,但对基于
索引的成员访问支持性能较差
成员可为任意
Object 子类的对象
在“集合框架”中有两种常规的 List 实现: ArrayList 和 Linked
List 。使用两种 List 实现的哪一种取决于您特定的需要。如果要支
持随机访问,而不必在除尾部的任何位置插入或除去元素,那么, Arr
青菜制作 qq :83395110
12/7/2009 5:17 PM
ayList 提供了可选的集合。但如果,您要频繁的从列表的中间位置添
加和除去元素,而只要顺序的访问列表元素,那么, LinkedList 实现
更好。
我们以 ArrayList 为例,先看一个简单的例子:
例子中,我们把 12 个月份存放到 ArrayList 中,然后用一个循环,
并使用 get()方法将列表中的对象都取出来。
而 LinkedList 添加了一些处理列表两端元素的方法(下图只显示了新方
法):
使用这些新方法,您就可以轻松的把 LinkedList 当作一个堆栈、队列或
其它面向端点的数据结构。
我们再来看另外一个使用 LinkedList 来实现一个简单的队列的例子:
import java.util.*;
public class ListExample {
public static void main(String args[]) {
LinkedList queue = new LinkedList();
queue.addFirst("Bernadine");
queue.addFirst("Elizabeth");
queue.addFirst("Gene");
queue.addFirst("Elizabeth");
queue.addFirst("Clara");
System.out.println(queue);
queue.removeLast();
queue.removeLast();
System.out.println(queue);
}
}
运行程序产生了以下输出。请注意,与 Set 不同的是 List 允许重复。
[Clara, Elizabeth, Gene, Elizabeth, Bernadine]
[Clara, Elizabeth, Gene]
该的程序演示了具体 List类的使用。第一部分,创建一个由 ArrayLis
t 支持的 List。填充完列表以后,特定条目就得到了。示例的 LinkedList
部分把 LinkedList 当作一个队列,从队列头部添加东西,从尾部除去。
List 接口不但以位置友好的方式遍历整个列表,还能处理集合的子集:
青菜制作 qq :83395110
12/7/2009 5:17 PM
? ListIterator listIterator() :返回一个 ListIterator 跌代器,
默认开始位置为0
? ListIterator listIterator(int startIndex) :返回一个 ListIterat
or 跌代器,开始位置为startIndex
? List subList(intfromIndex, int toIndex) :返回一个子列表
List ,元素存放为从 fromIndex 到 toIndex 之前的一个元素。
处理 subList() 时,位于 fromIndex 的元素在子列表中,而位于 to
Index 的元素则不是,提醒这一点很重要。以下 for-loop 测试案例大致反
映了这一点:
for (int i=fromIndex; i<toIndex; i++) {
// process element at position i
}
此外,我们还应该提醒的是:对子列表的更改(如 add()、remove() 和
set() 调用)对底层 List 也有影响。
ListIterator 接口
ListIterator 接口继承 Iterator 接口以支持添加或更改底层集合中
的元素,还支持双向访问。
以下源代码演示了列表中的反向循环。请注意 ListIterator 最初位于
列表尾之后(list.size()),因为第一个元素的下标是 0。
List list =...;
ListIterator iterator = list.listIterator(list.size());
while (iterator.hasPrevious()) {
Object element = iterator.previous();
// Process element
}
正常情况下,不用 ListIterator 改变某次遍历集合元素的方向 — 向
前或者向后。虽然在技术上可能实现时,但在 previous() 后立刻调用 nex
t(),返回的是同一个元素。把调用 next() 和 previous() 的顺序颠倒一下,
结果相同。
我们看一个 List 的例子:
import java.util.*;
public class ListIteratorTest {
public static void main(String[]args) {
List list = new ArrayList();
青菜制作 qq :83395110
12/7/2009 5:17 PM
list.add("aaa");
list.add("bbb");
list.add("ccc");
list.add("ddd");
System.out.println("下标 0 开始: "+list.listIterator(0).
next());//next()
System.out.println("下标1 开始:"+list.listIterator(1).
next());
System.out.println("子 List 1-3:"+list.subList(1,3));
//子列表
ListIterator it = list.listIterator();//默认从下标 0 开

//隐式光标属性 add 操作 ,插入到当前的下标的前面
it.add("sss");
while(it.hasNext()){
System.out.println("next Index="+it.nextIndex()+",
Object="+it.next());
}
//set 属性
ListIterator it1 = list.listIterator();
it1.next();
it1.set("ooo");
ListIterator it2 = list.listIterator(list.size());//
下标
while(it2.hasPrevious()){
System.out.println("previous Index="+it2.previous
Index()+",Object="+it2.previous());
}
}
}
程序的执行结果为:
下标 0 开始:aaa
下标 1 开始:bbb
子 List 1-3:[bbb, ccc]
next Index=1,Object=aaa
next Index=2,Object=bbb
next Index=3,Object=ccc
next Index=4,Object=ddd
previous Index=4,Object=ddd
previous Index=3,Object=ccc
previous Index=2,Object=bbb
previous Index=1,Object=aaa
previous Index=0,Object=ooo
青菜制作 qq :83395110
12/7/2009 5:17 PM
我们还需要稍微再解释一下 add() 操作。添加一个元素会导致新
元素立刻被添加到隐式光标的前面。因此,添加元素后调用 previous
() 会返回新元素,而调用 next() 则不起作用,返回添加操作之前的
下一个元素。下标的显示方式,如下图所示:
对于 List 的基本用法我们学会了,下面我们来进一步了解一下 List 的实现
原理,以便价升我们对于集合的理解。
1.3.3 实现原理
前面已经提了一下 Collection 的实现基础都是基于数组的。下面我
们就已 ArrayList为例,简单分析一下 ArrayList 列表的实现方式。首
先,先看下它的构造函数。
下列表格是在 SUN 提供的 API中的描述:
ArrayList() Constructs anemptylist with an initial capacity of ten.
ArrayList(Collection c) Constructs a list containing the elements of the
specified collection, inthe order they are returned by the collection's iterator.
ArrayList(int initialCapacity) Constructs an empty list with the
specified initialcapacity.
其中第一个构造函数 ArrayList() 和第二构造函数 ArrayList(Collectio
n c) 是按照 Collection 接口文档所述,所应该提供两个构造函数,
一个无参数,一个接受另一个 Collection 。
第 3 个构造函数:
ArrayList(int initialCapacity) 是 ArrayList 实现的比较重要的
构造函数,虽然,我们不常用它,但是某认的构造函数正是调用的该带参数:i
nitialCapacity 的构造函数来实现的。 其中参数:initialCapacity 表
示我们构造的这个 ArrayList 列表的初始化容量是多大。如果调用默认的构造
函数,则表示默认调用该参数为 initialCapacity =10 的方式,来进行构建
一个 ArrayList 列表对象。
为了更好的理解这个 initialCapacity 参数的概念,我们先看看 Array
List 在 Sun 提供的源码中的实现方式。先看一下它的属性有哪些:
ArrayList 继承了 AbstractList 我们主要看看 ArrayList 中的属性
就可以了。
ArrayList 中主要包含 2 个属性:
青菜制作 qq :83395110
12/7/2009 5:17 PM
? private transient Object elementData[];
? private int size;
其中数组:: elementData[] 是列表的实现核心属性:数组。 我们使用
该数组来进行存放集合中的数据。 而我们的初始化参数就是该数组构建时候的长
度,即该数组的 length 属性就是 initialCapacity 参数。
Keys:transient 表示被修饰的属性不是对象持久状态的一部分,不会自
动的序列化。
第 2 个属性: size 表示列表中真实数据的存放个数。
我们再来看一下 ArrayList 的构造函数,加深一下 ArrayList 是基于数
组的理解。
从源码中可以看到默认的构造函数调用的就是带参数的构造函数:
public ArrayList(int initialCapacity)
不过参数 initialCapacity=10 。
我们主要看 ArrayList(int initialCapacity) 这个构造函数。可以
看到:
this.elementData = new Object[initialCapacity];
我们就是使用的 initialCapacity 这个参数来创建一个 Object 数组。
而我们所有的往该集合对象中存放的数据, 就是存放到了这个 Object 数组中去
了。
我们在看看另外一个构造函数的源码:
这里,我们先看 size() 方法的实现形式。它的作用即是返回 size 属性值
的大小。然后我们再看另外一个构造函数 public ArrayList(Collection
c) ,该构造函数的作用是把另外一个容器对象中的元素存放到当前的 List
对象中。
可以看到,首先,我们是通过调用另外一个容器对象 C 的方法 size()来设
置当前的 List 对象的 size 属性的长度大小。
接下来,就是对 elementData 数组进行初始化,初始化的大小为原
先容器大小的 1.1 倍。最后,就是通过使用容器接口中的 Object[] toArray(O
bject[] a) 方法来把当前容器中的对象都存放到新的数组 elementDat
a 中。这样就完成了一个 ArrayList 的建立。
青菜制作 qq :83395110
12/7/2009 5:17 PM
可能大家会存在一个问题,那就是,我们建立的这个 ArrayList 是使用
数组来实现的,但是数组的长度一旦被定下来,就不能改变了。而我们在给 Ar
rayList 对象中添加元素的时候,却没有长度限制。这个时候,ArrayList
中的 elementData 属性就必须存在一个需要动态的扩充容量的机制。我们看
下面的代码,它描述了这个扩充机制:
这个方法的作用就是用来判断当前的数组是否需要扩容,应该扩容多少。其
中属性:modCount 是继承自父类,它表示当前的对象对 elementData 数组进
行了多少次扩容,清空,移除等操作。该属性相当于是一个对于当前 List 对
象的一个操作记录日志号。 我们主要看下面的代码实现:
1. 首先得到当前elementData 属性的长度 oldCapacity。
2. 然后通过判断 oldCapacity 和 minCapacity 参数谁大来决定是
否需要扩容
如果 minCapacity 大于 oldCapacity,那么我们就对当前
的 List 对象进行扩容。扩容的的策略为:取(oldCapacity *
3)/2 + 1 和 minCapacity 之间更大的那个。然后使用数组拷
贝的方法,把以前存放的数据转移到新的数组对象中
如果 minCapacity 不大于 oldCapacity 那么就不进行扩容。
下面我们看看上的那个 ensureCapacity方法的是如何使用的:
上的两个add方法都是往 List 中添加元素。每次在添加元素的时候,
我们就需要判断一下,是否需要对于当前的数组进行扩容。
我们主要看看 publicboolean add(Object o)方法,可以发现在添
加一个元素到容器中的时候,首先我们会判断是否需要扩容。因为只增加一个元
素,所以扩容的大小判断也就为当前的 size+1 来进行判断。然后,就把新添加
的元素放到数组 elementData中。
第二个方法 public boolean addAll(Collection c)也是同样的原
理。将新的元素放到 elementData数组之后。同时改变当前 List 对象的 si
ze 属性。
类似的 List 中的其他的方法也都是基于数组进行操作的。大家有兴趣
可以看看源码中的更多的实现方式。
最后我们再看看如何判断在集合中是否已经存在某一个对象的:
由源码中我们可以看到,public boolean contains(Object elem)方
法是通过调用 public int indexOf(Object elem)方法来判断是否在集合
中存在某个对象 elem。我们看看 indexOf 方法的具体实现。
? 首先我们判断一下 elem 对象是否为 null , 如果为 null 的话,
那么遍历数组 elementData 把第一个出现 null 的位置返回。
? 如果 elem 不为 null 的话,我们也是遍历数组 elementData ,
并通过调用 elem 对象的 equals()方法来得到第一个相等的元素的
位置。
这里我们可以发现,ArrayList 中用来判断是否包含一个对象,调用的是
各个对象自己实现的 equals()方法。在前面的高级特性里面,我们可以知道:
青菜制作 qq :83395110
12/7/2009 5:17 PM
如果要判断一个类的一个实例对象是否等于另外一个对象, 那么我们就需要自己
覆写 Object 类的 public boolean equals(Object obj) 方法。如果不
覆写该方法的话,那么就会调用 Object 的 equals()方法来进行判断。这就相
当于比较两个对象的内存应用地址是否相等了。
在集合框架中,不仅仅是 List,所有的集合类,如果需要判断里面是否
存放了的某个对象,都是调用该对象的 equals()方法来进行处理的。
1.4 Map
1.4.1 概述
数学中的映射关系在 Java 中就是通过 Map 来实现的。它表示,里面存储的
元素是一个对( pair ),我们通过一个对象,可以在这个映射关系中找到另外一
个和这个对象相关的东西。
前面提到的我们对于根据帐号名得到对应的人员的信息, 就属于这种情况的
应用。我们讲一个人员的帐户名和这人员的信息作了一个映射关系,也就是说,
我们把帐户名和人员信息当成了一个擌键值对斣 , 摤键旤就是帐户名, 摤值數就是
人员信息。下面我们先看看 Map 接口的常用方法。
1.4.2 常用方法
Map 接口不是 Collection 接口的继承。而是从自己的用于维护键-值
关联的接口层次结构入手。按定义,该接口描述了从不重复的键到值的映射。
我们可以把这个接口方法分成三组操作:改变、查询和提供可选视图。
改变操作允许您从映射中添加和除去键-值对。键和值都可以为null。但
是,您不能把 Map 作为一个键或值添加给自身。
? Object put(Object key,Object value):用来存放一
个键-值对 Map 中
? Object remove(Object key):根据key(键),移除一
个键-值对,并将值返回
? void putAll(Map mapping) :将另外一个 Map 中的元
青菜制作 qq :83395110
12/7/2009 5:17 PM
素存入当前的 Map 中
? void clear() :清空当前 Map 中的元素
查询操作允许您检查映射内容:
? Object get(Object key) :根据 key(键)取得对应的

? boolean containsKey(Object key) :判断 Map 中是
否存在某键(key)
? boolean containsValue(Objectvalue):判断 Map
中是否存在某值(value)
? int size():返回 Map 中 键-值对的个数
? boolean isEmpty() :判断当前 Map 是否为空
最后一组方法允许您把键或值的组作为集合来处理。
? public Set keySet():返回所有的键(key) ,并使用
Set 容器存放
? public Collection values() :返回所有的值(Valu
e) ,并使用 Collection 存放
? public Set entrySet(): 返回一个实现 Map.Entry 接
口的元素 Set
因为映射中键的集合必须是唯一的,就使用 Set 来支持。因为映射中值的
集合可能不唯一,就使用 Collection 来支持。最后一个方法返回一个实现
Map.Entry 接口的元素 Set。
我们看看 Map 的常用实现类的比较,如下表:
简述 实现 操作特性 成员要求
青菜制作 qq :83395110
12/7/2009 5:17 PM
HashMap
能满足用户对 Map 的
通用需求
键成员可为任意
Object 子类的对象,但
如果覆盖了equals 方
法,同时注意修改
hashCode 方法。
TreeMap
支持对键有序地遍历,
使用时建议先用
HashMap 增加和删除
成员,最后从
HashMap 生成
TreeMap; 附加实现
了 SortedMap 接口,
支持子 Map 等要求顺
序的操作
键成员要求实现
Comparable 接口, 或者
使用 Comparator 构造
TreeMap 键成员一般
为同一类型。
Map
保存键值对
成员,基于
键找值操
作,使用
compareTo
或 compare
方法对键进
行排序
LinkedHashMap
保留键的插入顺序, 用
equals 方法检查键和
值的相等性
成员可为任意 Object
子类的对象,但如果覆
盖了 equals 方法, 同时
注意修改 hashCode 方
法。
下面我们看一个简单的例子:
import java.util.*;
public class MapTest {
public static void main(String[]args) {
Map map1 = new HashMap();
Map map2 = new HashMap();
map1.put("1","aaa1");
map1.put("2","bbb2");
map2.put("10","aaaa10");
map2.put("11","bbbb11");
//根据键 "1" 取得值:"aaa1"
System.out.println("map1.get(\"1\")="+map1.get("1
"));
// 根据键 "1" 移除键值对"1"-"aaa1"
System.out.println("map1.remove(\"1\")="+map1.remove
("1"));
System.out.println("map1.get(\"1\")="+map1.get("1
"));
map1.putAll(map2);//将 map2 全部元素放入 map1 中
map2.clear();//清空 map2
System.out.println("map1 IsEmpty?="+map1.isEmpty());
System.out.println("map2 IsEmpty?="+map2.isEmpty());
System.out.println("map1中的键值对的个数 size = "+map1.
size());
System.out.println("KeySet="+map1.keySet());//set
青菜制作 qq :83395110
12/7/2009 5:17 PM
System.out.println("values="+map1.values());//Collec
tion
System.out.println("entrySet="+map1.entrySet());
System.out.println("map1 是否包含键:11 = "+map1.conta
insKey("11"));
System.out.println("map1 是否包含值:aaa1 = "+map1.con
tainsValue("aaa1"));
}
}
运行输出结果为:
map1.get("1")=aaa1
map1.remove("1")=aaa1
map1.get("1")=null
map1 IsEmpty?=false
map2 IsEmpty?=true
map1 中的键值对的个数 size= 3
KeySet=[10, 2, 11]
values=[aaaa10, bbb2, bbbb11]
entrySet=[10=aaaa10, 2=bbb2, 11=bbbb11]
map1 是否包含键:11 = true
map1 是否包含值:aaa1 = false
在该例子中,我们创建一个 HashMap,并使用了一下 Map 接口中的
各个方法。
其中 Map 中的 entrySet()方法先提一下,该 方法返回一个实现 Map.E
ntry 接口的对象集合。集合中每个对象都是底层 Map 中一个特定的
键-值对。
Map.Entry 接口是 Map 接口中的一个内部接口,该内部接口的实现类存
放的是键值对。在下面的实现原理中,我们会对这方面再作介绍,现在我们先不
管这个它的具体实现。
我们再看看排序的 Map 是如何使用:
import java.util.*;
public class MapSortExample {
public static void main(String args[]) {
Map map1 = new HashMap();
Map map2 = new LinkedHashMap();
for(int i=0;i<10;i++){
double s=Math.random()*100;//产生一个随机数,并将其放入
Map 中
青菜制作 qq :83395110
12/7/2009 5:17 PM
map1.put(new Integer((int)s),"第"+i+" 个放入的元素:
"+s+"\n");
map2.put(new Integer((int)s),"第"+i+" 个放入的元素:
"+s+"\n");
}
System.out.println("未排序前HashMap:"+map1);
System.out.println("未排序前LinkedHashMap:"+map2);
//使用 TreeMap 来对另外的 Map 进行重构和排序
Map sortedMap = new TreeMap(map1);
System.out.println("排序后:"+sortedMap);
System.out.println("排序后:"+new TreeMap(map2));
}
}
该程序的一次运行结果为:
未排序前 HashMap:{64=第 1 个放入的元素:64.05341725531845
, 15=第 9 个放入的元素:15.249165766266382
, 2=第 4 个放入的元素:2.66794706854534
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
, 60=第 8 个放入的元素:60.91451419025399
, 6=第 3 个放入的元素:6.286974058646977
, 1=第 7 个放入的元素:1.8261658496439903
, 48=第 6 个放入的元素:48.736039522423106
}
未排序前 LinkedHashMap: {77=第 0 个放入的元素: 77.28814965781416
, 64=第 1 个放入的元素:64.05341725531845
, 99=第 2 个放入的元素:99.99412014935982
, 6=第 3 个放入的元素:6.286974058646977
, 2=第 4 个放入的元素:2.66794706854534
, 97=第 5 个放入的元素:97.32893518378948
, 48=第 6 个放入的元素:48.736039522423106
, 1=第 7 个放入的元素:1.8261658496439903
, 60=第 8 个放入的元素:60.91451419025399
, 15=第 9 个放入的元素:15.249165766266382
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
青菜制作 qq :83395110
12/7/2009 5:17 PM
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
排序后:{1=第 7 个放入的元素:1.8261658496439903
, 2=第 4 个放入的元素:2.66794706854534
, 6=第 3 个放入的元素:6.286974058646977
, 15=第 9 个放入的元素:15.249165766266382
, 48=第 6 个放入的元素:48.736039522423106
, 60=第 8 个放入的元素:60.91451419025399
, 64=第 1 个放入的元素:64.05341725531845
, 77=第 0 个放入的元素:77.28814965781416
, 97=第 5 个放入的元素:97.32893518378948
, 99=第 2 个放入的元素:99.99412014935982
}
从运行结果,我们可以看出,HashMap 的存入顺序和输出顺序无关。而 Li
nkedHashMap 则保留了键值对的存入顺序。TreeMap 则是对 Map 中的元素进
行排序。在实际的使用中我们也经常这样做:使用 HashMap 或者 LinkedHash
Map 来存放元素,当所有的元素都存放完成后,如果使用则是需要一个经过排
序的 Map 的话,我们再使用 TreeMap 来重构原来的 Map 对象。这样做的好处
是:因为 HashMap 和 LinkedHashMap 存储数据的速度比直接使用 TreeMap
要快,存取效率要高。当完成了所有的元素的存放后,我们再对整个的 Map 中
的元素进行排序。这样可以提高整个程序的运行的效率,缩短执行时间。
这里需要注意的是,TreeMap 中是根据键(Key)进行排序的。而如果我
们要使用 TreeMap 来进行正常的排序的话,Key 中存放的对象必须实现 Com
parable 接口。
我们简单介绍一下这个接口:
1.4.3 Comparable 接口
在 java.lang 包中,Comparable 接口适用于一个类有自然顺序的时
候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。
它只有一个方法:compareTo() 方法,用来比较当前实例和作为参数传入
的元素。如果排序过程中当前实例出现在参数前(当前实例比参数大),就返回
某个负值。如果当前实例出现在参数后(当前实例比参数小),则返回正值。否
青菜制作 qq :83395110
12/7/2009 5:17 PM
则,返回零。如果这里不要求零返回值表示元素相等。零返回值可以只是表示两
个对象在排序的时候排在同一个位置。
上面例子中的整形的包装类:Integer 就实现了该接口。我们可以看一下
这个类的源码:
可以看到 compareTo 方法里面通过判断当前的 Integer 对象的值是
否大于传入的参数的值来得到返回值的。
在 Java 2 SDK,版本 1.2中有十四个类实现 Comparable
接口。下表展示了它们的自然排序。虽然一些类共享同一种自然排序,
但只有相互可比的类才能排序。
类 排序
BigDecimal,
BigInteger, Byte,
Double, Float,
Integer, Long, Short
按数字大小排序
Character 按 Unicode 值的数字大小排序
CollationKey 按语言环境敏感的字符串排序
Date 按年代排序
File
按系统特定的路径名的全限定字符的 Unicode
值排序
ObjectStreamField 按名字中字符的 Unicode 值排序
String 按字符串中字符 Unicode 值排序
这里只是简单的介绍一下排序接口,如果要详细的了解排序部分内容的话,
可以参考文章最后的附录部分对于排序的更加详细的描述。
我们再回到 Map 中来,Java 提高的 API 中除了上面介绍的几种 Map 比较
常用以为还有一些 Map,大家可以了解一下:
? WeakHashMap: WeakHashMap 是 Map 的一个特殊实现,它
只用于存储对键的弱引用。 当映射的某个键在 WeakHashMap
的外部不再被引用时,就允许垃圾收集器收集映射中相应的键
青菜制作 qq :83395110
12/7/2009 5:17 PM
值对。使用 WeakHashMap 有益于保持类似注册表的数据结
构, 其中条目的键不再能被任何线程访问时, 此条目就没用了。
? IdentifyHashMap: Map 的一种特性实现,关键属性的 hash 码不
是由 hashCode()方法计算,而是由 System.identityHashCode
方法计算,使用==进行比较而不是 equals()方法。
通过简单的对与 Map 中各个常用实现类的使用,为了更好的理解 Map,下面
我们再来了解一下 Map 的实现原理。
1.4.4 实现原理
有的人可能会认为 Map 会继承 Collection。在数学中,映射只是对(p
air)的集合。但是,在撢集合框架斳中,接口 Map 和 Collection 在层次
结构没有任何亲缘关系,它们是截然不同的。这种差别的原因与 Set 和 Map
在 Java 库中使用的方法有关。Map 的典型应用是访问按关键字存储的值。它
支持一系列集合操作的全部,但操作的是键-值对,而不是单个独立的元素。因
此 Map 需要支持 get() 和 put() 的基本操作,而 Set 不需要。此外,还
有返回 Map 对象的 Set 视图的方法:
Set set = aMap.keySet();
下面我们以 HashMap 为例,对 Map 的实现机制作一下更加深入
一点的理解。
因为 HashMap 里面使用 Hash 算法,所以在理解 HashMap 之前,我
们需要先了解一下 Hash 算法和 Hash 表。
Hash,一般翻译做“散列”,也有直接音译为"哈希"的,就是把任意
长度的输入(又叫做 预映射, pre-image),通过散列算法,变换成固
定长度的输出,该输出就是散列值。这种转换是一种压缩映射,也就是,
散列值的空间通常远小于输入的空间,不同的输入可能 会散列成相同
的输出,而不可能从散列值来唯一的确定输入值。
说的通俗一点,Hash 算法的意义在于提供了一种快速存取数据的方
法,它用一种算法建立键值与真实值之间的对应关系,(每一个真实值只
能有一个键值,但是一个键值可以对应多个真实值),这样可以快速在数
组等里面存取数据。
我们建立一个 HashTable(哈希表),该表的长度为 N,然后我们
分别在该表中的格子中存放不同的元素。每个格子下面存放的元素又是
以链表的方式存放元素。
? 当添加一个新的元素 Entry 的时候, 首先我们通过一个 Hash函
数计算出这个 Entry 元素的 Hash 值 hashcode。通过该 hashcode
值,就可以直接定位出我们应该把这个 Entry元素存入到 Hash表
的哪个格子中,如果该格子中已经存在元素了,那么只要把新的
Entry 元存放到这个链表中即可。
青菜制作 qq :83395110
12/7/2009 5:17 PM
? 如果要查找一个元素 Entry 的时候,也同样的方式,通过 Hash
函数计算出这个 Entry 元素的 Hash 值 hashcode。然后通过该 has
hcode值,就可以直接找到这个 Entry 是存放到哪个格子中的。接
下来就对该格子存放的链表元素进行逐个的比较查找就可以了。
举一个比较简单的例子来说明这个算法的运算方式:
假定我们有一个长度为 8 的 Hash 表(可以理解为一个长度为 8 的
数组)。在这个 Hash 表中存放数字:如下表
0 1 2 3 4 5 6 7
假定我们的 Hash 函数为:
Hashcode = X%8 -------- 对 8 取余数
其中 X 就是我们需要放入 Hash 表中的数字,而这个函数返回的 Ha
shcode 就是 Hash 码。
假定我们有下面 10 个数字需要依次存入到这个 Hash 表中:
11 , 23 , 44 , 9 , 6 , 32 , 12 , 45 , 57 , 89
通过上面的 Hash 函数,我们可以得到分别对应的 Hash 码:
11――3 ; 23――7 ;44――4 ;9――1;6――6;32――0;1
2――4;45――5;57――1;89――1;
计算出来的 Hash 码分别代表,该数字应该存放到 Hash表中的哪个
对应数字的格子中。如果改格子中已经有数字存在了,那么就以链表的
方式将数字依次存放在该格子中,如下表:
0 1 2 3 4 5 6 7
32 9 11 44 45 6 23
57 12
89
Hash 表和 Hash 算法的特点就是它的存取速度比数组差一些,但是比起单纯的链表,在查找
和存储方面却要好很多。同时数组也不利于数据的重构而排序等方面的要求。
更具体的说明,读者可以参考数据结构相关方面的书籍。
简单的了解了一下 Hash 算法后,我们就来看看 HashMap 的属性有哪些:
里面最重要的3 个属性:
transient Entry[] table: 用来存放键值对的对象 Entry 数组,也就是 Hash 表
transient int size:当前 Map 中存放的键值对的个数
final float loadFactor:负载因子,用来决定什么情况下应该对 Entry 进行扩容
我们 Entry 对象是 Map 接口中的一个内部接口。即是使用它来保存我们的键值对的。
我们看看这个 Entry 内部接口在 HashMap 中的实现:
通过查看源码,我们可以看到 Entry 类有点类似一个单向链表。其中:
final Objectkey 和 Object value;存放的就是我们放入 Map 中的键值对。
而属性 Entry next;表示当前键值对的下一个键值对是哪个 Entry。
青菜制作 qq :83395110
12/7/2009 5:17 PM
接下来,我们看看 HashMap 的主要的构造函数:
我们主要看看 public HashMap(int initialCapacity, float loadFactor)
因为,另外两个构造函数实行也是同样的方式进行构建一个HashMap 的。
该构造函数:
1. 首先是判断参数 int initialCapacity 和 float load
Factor 是否合法
2. 然后确定 Hash 表的初始化长度。确定的策略是:通过传进
来的参数 initialCapacity 来找出第一个大于它的 2 的次
方的数。比如说我们传了 18 这样的一个 initialCapacity
参数,那么真实的 table 数组的长度为 2 的 5 次方,即 32。
之所以采用这种策略来构建 Hash 表的长度, 是因为 2的次方
的运算对于现代的处理器来说,可以通过一些方法得到更加好
的执行效率。
3. 接下来就是得到重构因子(threshold)了,这个属性也
是 HashMap 中的一个比较重要的属性,它表示,当Hash 表
中的元素被存放了多少个之后, 我们就需要对该 Hash表进行
重构。
4. 最后就是使用得到的初始化参数 capacity 来构建 Hash
表:Entry[] table。
下面我们看看一个键值对是如何添加到 HashMap 中的。
该 put 方法是用来添加一个键值对(key-value)到 Map 中,如果
Map 中已经存在相同的键的键值对的话,那么就把新的值覆盖老的值,
并把老的值返回给方法的调用者。如果不存在一样的键,那么就返回 n
ull 。我们看看方法的具体实现:
1. 首先我们判断如果 key 为 null 则使用一个常量来代替该 key
值,该行为在方法 maskNull()终将 key替换为一个非 null
的对象 k。
2. 计算 key 值的Hash 码:hash
3. 通过使用 Hash 码来定位,我们应该把当前的键值对存放到 H
ash 表中的哪个格子中。indexFor()方法计算出的结果:i 就是 H
ash 表(table)中的下标。
青菜制作 qq :83395110
12/7/2009 5:17 PM
4. 然后遍历当前的 Hash 表中 table[i]格中的链表。 从中判断已否
已经存在一样的键(Key)的键值对。如果存在一样的 key 的键,
那么就用新的 value 覆写老的 value,并把老的 value 返回
5. 如果遍历后发现没有存在同样的键值对,那么就增加当前键
值对到 Hash 表中的第 i 个格子中的链表中。并返回 null 。
最后我们看看一个键值对是如何添加到各个格子中的链表中的:
我们先看 void addEntry(int hash, Object key, Object
value, int bucketIndex)方法,该方法的作用就用来添加一个键
值对到 Hash 表的第 bucketIndex 个格子中的链表中去。这个方法作
的工作就是:
1. 创建一个 Entry 对象用来存放键值对。
2. 添加该键值对 ---- Entry 对象到链表中
3. 最后在 size 属性加一,并判断是否需要对当前的 Hash 表进
行重构。如果需要就在 void resize(intnewCapacity)方法中
进行重构。
之所以需要重构,也是基于性能考虑。大家可以考虑这样一种情况,
假定我们的 Hash 表只有 4 个格子,那么我们所有的数据都是放到这 4
个格子中。如果存储的数据量比较大的话,例如 100。这个时候,我们
就会发现,在这个 Hash 表中的 4 个格子存放的 4 个长长的链表。而我
们每次查找元素的时候,其实相当于就是遍历链表了。这种情况下,我
们用这个 Hash 表来存取数据的性能实际上和使用链表差不多了。
但是如果我们对这个 Hash 表进行重构, 换为使用 Hash 表长度为 2
00 的表来存储这 100 个数据,那么平均 2 个格子里面才会存放一个数
据。这个时候我们查找的数据的速度就会非常的快。因为基本上每个格
子中存放的链表都不会很长,所以我们遍历链表的次数也就很少,这样
也就加快了查找速度。但是这个时候又存在了另外的一个问题。我们使
用了至少 200 个数据的空间来存放 100 个数据,这样就造成至少 100
个数据空间的浪费。 在速度和空间上面,我们需要找到一个适合自己
的中间值。在 HashMap 中我们通过负载因子(loadFactor)来决定
应该什么时候应该重构我们的 Hash 表,以达到比较好的性能状态。
青菜制作 qq :83395110
12/7/2009 5:17 PM
我们再看看重构 Hash 表的方法: void resize(int newCapacity)
是如何实现的:
它的实现方式比较简单:
1. 首先判断如果 Hash 表的长度已经达到最大值,那么就不进行
重构了。因为这个时候 Hash 表的长度已经达到上限,已经没有
必要重构了。
2. 然后就是构建新的 Hash 表
3. 把老的 Hash 表中的对象数据全部转移到新的 Hash 表 newTab
le 中,并设置新的重构因子 threshold
对于 HashMap 中的实现原理, 我们就分析到这里。 大家可能会发现,
HashCode 的计算,是用来定位我们的键值对应该放到 Hash 表中哪个
格子中的关键属性。 而这个 HashCode 的计算方法是调用的各个对象自
己的实现的 hashCode()方法。而这个方法是在 Object 对象中定义
的,所以我们自己定义的类如果要在集合中使用的话,就需要正确的覆
写 hashCode() 方法。下面就介绍一下应该如何正确覆写 hashCode
()方法。
1.4.5 覆写 hashCode()
在明白了 HashMap 具有哪些功能,以及实现原理后,了解如何写一
个 hashCode()方法就更有意义了。当然,在 HashMap 中存取一个键
值对涉及到的另外一个方法为 equals (),因为该方法的覆写在高级特
性已经讲解了。这里就不做过多的描述。
设计 hashCode()时最重要的因素就是:无论何时,对同一个
对象调用 hashCode()都应该生成同样的值。如果在将一个对象用 pu
t()方法添加进 HashMap时产生一个 hashCode()值,而用 get()
青菜制作 qq :83395110
12/7/2009 5:17 PM
取出时却产生了另外一个 hashCode()值, 那么就无法重新取得该对象
了。所以,如果你的 hashCode()方法依赖于对象中易变的数据,那用
户就要小心了,因为此数据发生变化时,hashCode()就会产生一个不
同的 hash 码,相当于产生了一个不同的撃键旤。
此外,也不应该使 hashCode()依赖于具有唯一性的对象信息,
尤其是使用 this 的值,这只能产生很糟糕的 hashCode()。因为这样
做无法生成一个新的 撃键旤 , 使之与 put()种原始的 撃键值对斣 中的 撃键旤
相同。例如,如果我们不覆写 Object 的 hashCode()方法,那么调用
该方法的时候,就会调用 Object 的 hashCode()方法的默认实现。O
bject 的 hashCode()方法,返回的是当前对象的内存地址。下次如
果我们需要取一个一样的撃键旤对应的键值对的时候,我们就无法得到
一样的 hashCode 值了。因为我们后来创建的撃键旤对象已经不是存入
HashMap 中的那个内存地址的对象了。
我们看一个简单的例子,就能更加清楚的理解上面的意思。假定我
们写了一个类:Person (人),我们判断一个对象擉人斔是否指向同
一个人,只要知道这个人的身份证号一直就可以了。
先看我们没有实现 hashCode 的情况:
package c08.hashEx;
import java.util.*;
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
青菜制作 qq :83395110
12/7/2009 5:17 PM
}
return false;
}
public String toString() {
return "身份证:"+id;
}
}
//人员信息类
class Person {
Code id;// 身份证
String name;// 姓名
public Person(String name, Code id) {
this.id=id;
this.name=name;
}
//如果身份证号相同,就表示两个人是同一个人
public boolean equals(Object anObject) {
if (anObject instanceof Person){
Person other=(Person) anObject;
return this.id.equals(other.id);
}
return false;
}
public String toString() {
return "姓名:"+name+" 身份证:"+id.id+"\n";
}
}
public class HashCodeEx {
public static void main(String[] args) {
HashMap map=new HashMap();
Person p1=new Person("张三",new Code(123));
map.put(p1.id,p1);//我们根据身份证来作为 key 值存放到 Map

Person p2=new Person("李四",new Code(456));
map.put(p2.id,p2);
Person p3=new Person("王二",new Code(789));
map.put(p3.id,p3);
System.out.println("HashMap中存放的人员信息:\n"+map);
// 张三 改名为:张山 但是还是同一个人。
Person p4=new Person("张山",new Code(123));
map.put(p4.id,p4);
System.out.println("张三改名后 HashMap 中存放的人员信
息:\n"+map);
//查找身份证为:123 的人员信息
青菜制作 qq :83395110
12/7/2009 5:17 PM
System.out.println("查找身份证为:123 的人员信
息:"+map.get(new Code(123)));
}
}
运行结果为:
HashMap中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三 身份证:123
, 身份证:789=姓名:王二 身份证:789
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:123=姓名:张三 身份证:123
, 身份证:123=姓名:张山 身份证:123
, 身份证:789=姓名:王二 身份证:789
}
查找身份证为:123 的人员信息:null
上面的例子的演示的是, 我们在一个 HashMap 中存放了一些人员的
信息。并以这些人员的身份证最为人员的撃键旤。当有的人员的姓名修
改了的情况下,我们需要更新这个 HashMap。同时假如我们知道某个
身份证号,想了解这个身份证号对应的人员信息如何,我们也可以根据
这个身份证号在 HashMap 中得到对应的信息。
而例子的输出结果表示,我们所做的更新和查找操作都失败了。失
败的原因就是我们的身份证类:Code 没有覆写 hashCode()方法。这
个时候,当查找一样的身份证号码的键值对的时候,使用的是默认的对
象的内存地址来进行定位。这样,后面的所有的身份证号对象 new Co
de(123) 产生的 hashCode()值都是不一样的。所以导致操作失败。
下面,我们给 Code 类加上 hashCode()方法,然后再运行一下
程序看看:
//身份证类
class Code{
final int id;//身份证号码已经确认,不能改变
Code(int i){
id=i;
}
//身份号号码相同,则身份证相同
public boolean equals(Object anObject) {
青菜制作 qq :83395110
12/7/2009 5:17 PM
if (anObject instanceof Code){
Code other=(Code) anObject;
return this.id==other.id;
}
return false;
}
public String toString() {
return "身份证:"+id;
}
//覆写 hashCode 方法,并使用身份证号作为 hash 值
public int hashCode(){
return id;
}
}
再次执行上面的 HashCodeEx 的结果就为:
HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张三 身份证:123
}
张三改名后 HashMap 中存放的人员信息:
{身份证:456=姓名:李四 身份证:456
, 身份证:789=姓名:王二 身份证:789
, 身份证:123=姓名:张山 身份证:123
}
查找身份证为:123 的人员信息:姓名:张山 身份证:123
这个时候,我们发现。我们想要做的更新和查找操作都成功了。
对于 Map 部分的使用和实现,主要就是需要注意存放撆键值对斣中
的对象的 equals()方法和hashCode()方法的覆写。如果需要使用到
排序的话,那么还需要实现 Comparable 接口中的 compareTo() 方法。
我们需要注意 Map 中的撃键旤是不能重复的,而是否重复的判断,是
通过调用撁键旤对象的 equals()方法来决定的。而在 HashMap 中
查找和存取摗键值对斣是同时使用 hashCode()方法和 equals()
方法来决定的。
1.5 Set
1.5.1 概述
青菜制作 qq :83395110
12/7/2009 5:17 PM
Java 中的 Set 和正好和数学上直观的集(set)的概念是相同的。Set 最大的
特性就是不允许在其中存放的元素是重复的。根据这个特点,我们就可以使用 S
et 这个接口来实现前面提到的关于商品种类的存储需求。Set 可以被用来过滤
在其他集合中存放的元素,从而得到一个没有包含重复新的集合。
1.5.2 常用方法
按照定义,Set 接口继承 Collection 接口,而且它不允许集合中存在重
复项。所有原始方法都是现成的,没有引入新方法。具体的 Set 实现类依赖添
加的对象的 equals() 方法来检查等同性。
我们简单的描述一下各个方法的作用:
? public int size() :返回 set 中元素的数目,如果 set 包含的元素数大于
Integer.MAX_VALUE,返回 Integer.MAX_VALUE
? public boolean isEmpty() :如果 set 中不含元素,返回 true
? public boolean contains(Object o) :如果 set 包含指定元素,返回 true
? public Iterator iterator()
? 返回 set 中元素的迭代器
? 元素返回没有特定的顺序,除非 set 是提高了该保证的某些类的
实例
? public Object[] toArray() :返回包含 set 中所有元素的数组
? public Object[] toArray(Object[] a) :返回包含 set 中所有元素的数组,
返回数组的运行时类型是指定数组的运行时类型
? public boolean add(Object o) :如果 set 中不存在指定元素,则向 set
加入
? public boolean remove(Object o) :如果 set 中存在指定元素,则从 set
中删除
? public boolean removeAll(Collection c) :如果 set 包含指定集合,则从
set 中删除指定集合的所有元素
? public boolean containsAll(Collection c) :如果 set 包含指定集合的所
有元素,返回true。如果指定集合也是一个 set,只有是当前 set 的子集时,
方法返回 true
? public boolean addAll(Collection c) :如果 set 中中不存在指定集合的
青菜制作 qq :83395110
12/7/2009 5:17 PM
元素,则向 set 中加入所有元素
? public boolean retainAll(Collection c) :只保留 set 中所含的指定集合
的元素(可选操作)。换言之,从 set 中删除所有指定集合不包含的元素。
如果指定集合也是一个 set,那么该操作修改 set 的效果是使它的值为两
个 set 的交集
? public boolean removeAll(Collection c) :如果 set 包含指定集合,则从
set 中删除指定集合的所有元素
? public void clear() :从 set 中删除所有元素
“集合框架” 支持 Set 接口两种普通的实现: HashSet 和 TreeSe
t 以及 LinkedHashSet。下表中是 Set 的常用实现类的描述:
简述 实现 操作特性 成员要求
HashSet 外部无序地遍历成员。
成员可为任意 Object 子类
的对象,但如果覆盖了
equals 方法, 同时注意修改
hashCode方法。
TreeSet
外部有序地遍历成员;
附加实现了 SortedSet, 支
持子集等要求顺序的操

成员要求实现 Comparable
接口, 或者使用 Comparator
构造 TreeSet。成员一般为
同一类型。
Set
成员不
能重复
LinkedHashSet
外部按成员的插入顺序
遍历成员
成员与 HashSet 成员类似
在更多情况下,您会使用 HashSet 存储重复自由的集合。同时 H
ashSet 中也是采用了 Hash 算法的方式进行存取对象元素的。所以添
加到 HashSet 的对象对应的类也需要采用恰当方式来实现 hashCo
de() 方法。虽然大多数系统类覆盖了 Object 中缺省的 hashCode
() 实现,但创建您自己的要添加到 HashSet 的类时,别忘了覆盖
hashCode()。
青菜制作 qq :83395110
12/7/2009 5:17 PM
对于 Set 的使用,我们先以一个简单的例子来说明:
import java.util.*;
public class HashSetDemo {
public static void main(String[]args) {
Set set1 = new HashSet();
if (set1.add("a")) {//添加成功
System.out.println("1 add true");
}
if (set1.add("a")) {//添加失败
System.out.println("2 add true");
}
set1.add("000");//添加对象到 Set 集合中
set1.add("111");
set1.add("222");
System.out.println("集合 set1 的大小:"+set1.size());
System.out.println("集合 set1 的内容:"+set1);
set1.remove("000");//从集合 set1 中移除掉 "000" 这个对象
System.out.println("集合 set1 移除 000 后的内容: "+set1);
System.out.println("集合 set1 中是否包含 000 :"+set1.co
ntains("000"));
System.out.println("集合 set1 中是否包含 111 :"+set1.co
ntains("111"));
Set set2=new HashSet();
set2.add("111");
set2.addAll(set1);//将 set1 集合中的元素全部都加到 set2 中
System.out.println("集合 set2 的内容:"+set2);
set2.clear();//清空集合 set1 中的元素
System.out.println("集合 set2 是否为空 :"+set2.isEmpty
());
Iterator iterator = set1.iterator();//得到一个迭代器
while (iterator.hasNext()) {//遍历
Object element = iterator.next();
System.out.println("iterator = " + element);
}
//将集合set1转化为数组
Object s[]= set1.toArray();
for(int i=0;i<s.length;i++){
System.out.println(s[i]);
}
}
}
程序执行的结果为:
青菜制作 qq :83395110
12/7/2009 5:17 PM
1 add true
集合 set1 的大小:4
集合 set1 的内容:[222, a, 000, 111]
集合 set1 移除 000 后的内容:[222,a, 111]
集合 set1 中是否包含 000 :false
集合 set1 中是否包含 111 :true
集合 set2 的内容:[222, a, 111]
集合 set2 是否为空 :true
iterator = 222
iterator = a
iterator = 111
222
a
111
从上面的这个简单的例子中,我们可以发现,Set 中的方法与直接使用 Co
llection 中的方法一样。唯一需要注意的就是 Set 中存放的元素不能重复。
我们再看一个例子,来了解一下其它的 Set 的实现类的特性:
packagec08;
import java.util.*;
public class SetSortExample {
public static void main(String args[]) {
Set set1 = new HashSet();
Set set2 = new LinkedHashSet();
for(int i=0;i<5;i++){
//产生一个随机数,并将其放入 Set 中
int s=(int) (Math.random()*100);
set1.add(new Integer( s));
set2.add(new Integer( s));
System.out.println("第 "+i+" 次随机数产生为:"+s);
}
System.out.println("未排序前 HashSet:"+set1);
System.out.println("未排序前 LinkedHashSet:"+set2);
//使用 TreeSet 来对另外的 Set 进行重构和排序
Set sortedSet = new TreeSet(set1);
System.out.println("排序后 TreeSet :"+sortedSet);
}
}
该程序的一次执行结果为:
第 0 次随机数产生为:96
第 1 次随机数产生为:64
青菜制作 qq :83395110
12/7/2009 5:17 PM
第 2 次随机数产生为:14
第 3 次随机数产生为:95
第 4 次随机数产生为:57
未排序前 HashSet:[64, 96, 95,57, 14]
未排序前 LinkedHashSet:[96, 64, 14, 95, 57]
排序后 TreeSet :[14, 57, 64, 95, 96]
从这个例子中, 我们可以知道 HashSet 的元素存放顺序和我们添加
进去时候的顺序没有任何关系,而 LinkedHashSet 则保持元素的添
加顺序。TreeSet 则是对我们的 Set 中的元素进行排序存放。
一般来说,当您要从集合中以有序的方式抽取元素时,TreeSet
实现就会有用处。为了能顺利进行,添加到 TreeSet的元素必须是可
排序的。 而您同样需要对添加到 TreeSet 中的类对象实现 Compara
ble 接口的支持。对于 Comparable 接口的实现,在前一小节的 Map
中已经简单的介绍了一下。我们暂且假定一棵树知道如何保持 java.
lang 包装程序器类元素的有序状态。一般说来,先把元素添加到 Ha
shSet,再把集合转换为 TreeSet 来进行有序遍历会更快。这点和 H
ashMap 的使用非常的类似。
其实 Set 的实现原理是基于 Map 上面的。通过下面我们对 Set 的
进一步分析大家就能更加清楚的了解这点了。
1.5.3 实现原理
Java 中 Set 的概念和数学中的集合(set)一致,都表示一个集内可以
存放的元素是不能重复的。
前面我们会发现,Set 中很多实现类和 Map中的一些实现类的使
用上非常的相似。而且前面再讲解 Map 的时候,我们也提到:Map 中
的撃键值对斣,其中的撃键旤是不能重复的。这个和 Set 中的元素不能
重复一致。我们以 HashSet 为例来分析一下,会发现其实 Set 利用的就
是 Map 中撔键旤不能重复的特性来实现的。
青菜制作 qq :83395110
12/7/2009 5:17 PM
先看看 HashSet 中的有哪些属性:
再结合构造函数来看看:
通过这些方法,我们可以发现,其实 HashSet 的实现,全部的操作
都是基于 HashMap 来进行的。我们看看是如何通过 HashMap 来保证我
们的 HashSet 的元素不重复性的:
看到这个操作我们可以发现 HashSet 的巧妙实现: 就是建立一个 擌键
值对斣,摤键旤就是我们要存入的对象,摤值數则是一个常量。这样可
以确保,我们所需要的存储的信息之是撉键旤。而擑键旤在 Map 中是
不能重复的,这就保证了我们存入 Set 中的所有的元素都不重复。而判
断是否添加元素成功,则是通过判断我们向 Map 中存入的撃键值对斣
是否已经存在,如果存在的话,那么返回值肯定是常量:PRESENT ,
表示添加失败。如果不存在,返回值就为 null 表示添加成功。
我们再看看其他的方法实现:
了解了这些后,我们就不难理解,为什么 HashMap 中需要注意
的地方,在 HashSet 中也同样的需要注意。其他的 Set 的实现类也是差
不多的原理。
至此对于 Set 我们就应该能够比较好的理解了。
1.6 总结:集合框架中常用类比较
用“集合框架”设计软件时, 记住该框架四个基本接口的下列层次结构关系会
有用处:
• Collection 接口是一组允许重复的对象。
• Set 接口继承 Collection,但不允许重复。
• List 接口继承Collection,允许重复,并引入位置下标。
• Map 接口既不继承 Set 也不继承 Collection, 存取的是键值对
我们以下面这个图表来描述一下常用的集合的实现类之间的区别:
Collection/Map 接口 成员重复性 元素存放顺序
(Ordered/Sorted)
元素中被调
用的方法
基于那中数据
结构来实现的
HashSet Set Unique elements No order equals() Hash 表
青菜制作 qq :83395110
12/7/2009 5:17 PM
Collection/Map 接口 成员重复性 元素存放顺序
(Ordered/Sorted)
元素中被调
用的方法
基于那中数据
结构来实现的
hashCode()
LinkedHashSet Set Unique elements Insertion order equals()
hashCode()
Hash 表和双向
链表
TreeSet SortedS
et
Unique elements Sorted equals()
compareTo()
平 衡 树
(Balanced tree)
ArrayList List Allowed Insertion order equals() 数组
LinkedList List Allowed Insertion order equals() 链表
Vector List Allowed Insertion order equals() 数组
HashMap Map Unique keys No order equals()
hashCode()
Hash 表
LinkedHashMa
p
Map Unique keys Key insertion
order/Access order of
entries
equals()
hashCode()
Hash 表和双向
链表
Hashtable Map Unique keys No order equals()
hashCode()
Hash 表
TreeMap Sorted
Map
Unique keys Sorted in key order equals()
compareTo()
平 衡 树
(Balanced tree)
2 练习
撰写一个 Person class,表示一个人员的信息。令该类具备多辆 Car 的信息,
表示一个人可以拥有的车子的数据,以及:
Certificate code: 身份证对象
name: 姓名
cash: 现金
List car; 拥有的汽车,其中存放的是 Car对象
boolean buycar(car); 买车子
boolean sellcar(Person p);//把自己全部的车子卖给别人
boolean buyCar(Car car,Person p);//自动查找卖车的人 p 是否有买主想
要买的车 car,如果有就买,并返回 true ,否则返回 false
viod addCar(car);//把某辆车送给方法的调用者。
String toString();//得到人的信息
并撰写第二个 Car class 具备的属性:
String ID; //ID 车牌号
cost //价格
青菜制作 qq :83395110
12/7/2009 5:17 PM
color //颜色
Person owner; //车子的拥有者
toString();//得到汽车的信息
equals();//比较车子是否同一俩汽车,ID 相同则认为相同
在另外一个 Market 类里面,进行车子的买卖。并保留所有交易人员
的的信息到一个 HashMap 中,我们可以通过身份证号来查找到对应的
人员的信息。同时所有的车子种类都在市场中进行注册,即车子的信息
使用一个 Set 来保存
属性:
HashMap people;//存放交易人员的信息。Key 为身份证号,valu
e 为 Person 对象
方法:
static boolean sellCar(Person p1 ,Car car1, Person p2);
//p1 将 car1 卖给 p2 。并在该方法中记录效益人的信息到 people
中。
撰写类 Certificate 表示身份证:
属性:
Id;//号码
方法:
equals();//比较两个身份证是否同一个,ID 相同则认为相同
hashCode();//正确编写 hashCode 方法
场景:
一个叫 Bob 的人:身份证:310 现金:30000。
有一辆车子:ID:001,红色,价格:50000 的车子;
一个叫 Tom 的人:身份证:210 现金:70000,
有一辆车子: 颜色:白色,ID:003, 价格:25000。
一个叫 King 的人:身份证:245 现金:60000,
有 2 辆车子: 颜色:白色,ID:005, 价格:18000。
颜色:红色,ID:045, 价格:58000。
Tom 买了Bob 的车子.他就拥有了 2 辆汽车
King 把 ID=005 的车子买给了 Bob
最后各人的信息如何?
3 附录:排序
为了用“集合框架”的额外部分把排序支持添加到 Java 2 SDK,版本 1.
2,核心 Java 库作了许多更改。像 String 和 Integer 类如今实现 Comp
arable 接口以提供自然排序顺序。对于那些没有自然顺序的类、或者当您想要
一个不同于自然顺序的顺序时,您可以实现 Comparator 接口来定义您自己
的。
青菜制作 qq :83395110
12/7/2009 5:17 PM
为了利用排序功能, “集合框架”提供了两种使用该功能的接口:SortedSet
和 SortedMap。
Comparable 接口
在 java.lang 包中,Comparable 接口适用于一个类有自然顺序的时
候。假定对象集合是同一类型,该接口允许您把集合排序成自然顺序。
compareTo() 方法比较当前实例和作为参数传入的元素。 如果排序过程中
当前实例出现在参数前,就返回某个负值。如果当前实例出现在参数后,则返回
正值。否则,返回零。这里不要求零返回值表示元素相等。零返回值只是表示两
个对象排在同一个位置。
在 Java 2 SDK,版本 1.2 中有十四个类实现 Comparable 接口。下
表展示了它们的自然排序。虽然一些类共享同一种自然排序,但只有相互可比的
类才能排序。
类 排序
BigDecimal, BigInteger,
Byte, Double, Float,
Integer,Long, Short
按数字大小排序
Character
按 Unicode 值的数字大小排序
CollationKey
按语言环境敏感的字符串排序
Date
按年代排序
File
按系统特定的路径名的全限定字符的 Unicode 值排序
ObjectStreamField
按名字中字符的 Unicode 值排序
String
按字符串中字符 Unicode 值排序
创建您自己的类 Comparable 只是个实现 compareTo() 方法的问题。
通常就是依赖几个数据成员的自然排序。您自己的类也应该覆盖 equals() 和
hashCode() 以确保两个相等的对象返回同一个散列码。
Comparator 接口
若一个类不能用于实现 java.lang.Comparable,您可以提供自己的 jav
a.util.Comparator 行为。如果您不喜欢缺省的 Comparable 行为,您照样
可以提供自己的 Comparator。
Comparator 的 compare() 方法的返回值和 Comparable 的 compa
青菜制作 qq :83395110
12/7/2009 5:17 PM
reTo() 方法的返回值相似。在此情况下,如果排序时第一个元素出现在第二个
元素之前,则返回一个负值。如果第一个元素出现在后,那么返回一个正值。否
则,返回零。与 Comparable 相似,零返回值不表示元素相等。一个零返回值
只是表示两个对象排在同一位置。由 Comparator 用户决定如何处理。如果两
个不相等的元素比较的结果为零,您首先应该确信那就是您要的结果,然后记录
行为。
为了演示,您会发现编写一个新的忽略大小写的 Comparator,代替使用
Collator 进行语言环境特定、忽略大小写的比较会更容易。这样的一种实现如
下所示:
class CaseInsensitiveComparator implements Comparator {
public int compare(Object element1, Object element2) {
String lowerE1 = ((String)element1).toLowerCase();
String lowerE2 = ((String)element2).toLowerCase();
return lowerE1.compareTo(lowerE2);
}
}
因为每个类在某些地方都建立了 Object 子类,所以这不是您实现 equal
s() 方法的必要条件。实际上大多数情况下您不会去这样做。切记该equals()
方法检查的是 Comparator 实现的等同性,不是处于比较状态下的对象。
Collections类有个预定义的 Comparator 用于重用。 调用 Collections.
reverseOrder() 返回一个 Comparator,它对逆序实现 Comparable 接口
的对象进行排序。
发布了5 篇原创文章 · 获赞 0 · 访问量 2万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览