一、集合类及其特点
在程序设计中,一个重要的组成部分就是如何有效地组织和表示数据通常,我们把用于存储和管理数据的实体称为数据结构而把一组元素按照一定的数据结构进行存储和管理的容器。就称为集合。通过数据结构,我们可以实现不同特性的集合。每个集合都可以保存一组其他类型的对象数据(被称为元素),但由于不同集合内部采用的数据结构有所不同(如数组,链表,二叉树等),所以这些集合的特性也各不相同。这就要求我们在使用集合时应事先确定程序设计的需求,否则有可能事倍功半。
集合类的特点如下:
(1)空间自主调整,提高空间利用率。
集合类在使用过程中可以根据需要自动完成空间的动态调整,从而满足实际需求。
(2)提供不同的数据结构和算法,减少编程工作量。
不同的集合类,其内部数据结构和实现算法各不相同,适用于不用的应用场合。在使用相应的集合类时,不需考虑内部实现细节即可完成数据的处理,大大减少了编程工作量。
(3)提高程序的处理速度和质量。
这些集合类对自身使用的数据结构和算法都进行了详细设计和优化,其运行速度和质量比用户自己编写的和处理要好很多。
在使用集合类时还需要注意以下两点:
一个是集合类不支持简单数据类型的存放和处理。如果确实需要存储简单类型数据可以先进行类的封装(装箱)处理。另一个是集合类中存放的是对象的引用,而不是对象本身。在这一章中为叙述方便,通常说的存储元素或对象,其本质均是对象的引用,这一点请注意。
Java的集合类
Java语言中提供了很多有用的集合类型。利用这些类型我们可以很方便地对元素进行存储、访问、查找、排序、删除等各种操作。这些集合类型统称为Java集合框架(JavaCollectionsFramework,JCF)。此框架的大部分类都封装在java.util包中。
集合类的分类方法有很多,既可以按其中元素的类型进行分类,也可以按应用在集合元素上的操作进行分类,常见的集合类有:
List集合:这是一组有序元素的集合,可以使用索引或顺序访问其中的元素。我们也称之为线性表。
Set集合:这是一组无序元素的集合,并且集合中的元素不允许重复。
Map集合:这是一组存放<键,值> 对元素的集合,每个元素都由一个唯一的键和相对应的值组成。这些键不允许重复,但值可以重复。我们称之为映射。
Queue集合:这是一个具有先进先出特性的集合,称之为队列。
1、集合主要是两组(单列集合 ,双列集合)
2、Collection 接口有两个重要的子接口 List Set ,他们的实现子类都是单列集合。
3、Map 接口的实现子类 是双列集合,存放的 K-V。
(1) Collection实现子类可以存放多个元素,每个元素可以是Obiect。
(2) 有些Collection的实现类,可以存放重复的元素,有些不可以。
(3) 有些Collection的实现类,有些是有序的(List),有些不是有序(Set)。
(4) Collection接口没有直接的实现子类,是通过它的子接口Set 和 List 来实现的。
Collection接口和常用方法:
Collection接口遍历元素方式1 : 使用lterator(迭代器)
① lterator 对象称为迭代器,主要用于遍历 Collection 集合中的元素
② 所有实现了Collection接口的集合类都有一个iterator0方法,用以返回一个实现了lterator接口的对象,即可以返回一个迭代器
③ lterator 仅用于遍历集合,lterator 本身并不存放对象
在调用iterator.next0方法之前必须要调用 iterator.hasNext() 进行检测。若不调用,且下一条记录无效,直接调用it.next0会抛出NoSuchElementException异常。
public static void main(String[] args) {
//声明一个 Collection 集合
Collection c= new ArrayList<>();
c.add("张三");
c.add("李四");
c.add("王五");
// 得到 c 对应的迭代器
Iterator iterator = c.iterator();
// 使用 while 循环遍历
while (iterator.hasNext()){ //判断是否还有数据
//返回下一个元素 ,类型是Object
Object obj=iterator.next();
System.out.println(obj);
}
}
测试结果为:
Collection接口遍历对象方式2:for循环增强
增强for循环,可以代替iterator迭代器,特点: 增强for就是简化版的iterator
本质一样。只能用于遍历集合或数组基本语法
for(元素类型 元素名 : 集合名或数组名) {
访问元素}
public static void main(String[] args) {
//声明一个 Collection 集合
Collection c= new ArrayList<>();
c.add("张三");
c.add("李四");
c.add("王五");
//增强 for循环
for (Object object:c){
System.out.println(object);
}
}
测试结果为:
练习题:
public static void main(String[] args) {
List list=new ArrayList();
list.add(new Dog("ll",12));
list.add(new Dog("hh",22));
list.add(new Dog("xx",4));
for (Object l:list){
System.out.println(l);
}
//迭代器
System.out.println("=========================");
Iterator iterator= list.iterator();
while (iterator.hasNext())
{
Object b=iterator.next();
System.out.println(b);
}
}
class Dog{
String name;
int age;
public Dog(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
测试结果为:
Collection 接口常用方法
Collection c= new ArrayList<>();
Collection c1= new ArrayList<>();
//向集合中添加元素
c.add("张三");
c.add("Tom");
c.add(45);
System.out.println(c);
//将指定集合中的所有元素添加到当前集合中
c1.add(c);
c1.add("aaa");
System.out.println(c1);
//删除当前集合中包含的指定元素
c.remove("张三");
System.out.println(c);
//删除当前集合中与指定集合相同的元素
c.removeAll(c1);
System.out.println(c);
//保留当前集合中与指定集合相同的元素
c.retainAll(c1);
//删除当前集合中所有的元素
c.clear();
//查找当前集合是否有指定元素
c.contains(45);
//查找当前集合中是否包含指定集合中的所有元素
c.containsAll(c1);
//当前集合是否为空
c.isEmpty();
//返回当前集合的元素个数
c.size();
//返回一个可遍历的当前集合的迭代器
c.iterator();
//返回一个连接集合的顺序流
c.stream();
//返回当前集合所有元素的数组
c.toArray();
Java中没有提供直接实现Collection接口的类,而是通过其他接口继承来进行实现。主要包括List集合接口和Set集合接口。List是一个有序元素集合,允许出现重复元素,而Set是一个无序集合,不允许出现重复元素。
Map是Java.util包中的另一个接口,它和Collection接口没有关系,但是也属于集合类的一部分。Map集合中的每个元素都由一个键一值对构成。在Map中不能有重复的键,但是可以有相同的值。集合类均采用泛型进行定义。
下面我们对这些集合进行详细介绍。
一、List 集合
List集合接口,我们也称之为线性表,这是一个有序列表,其与数组类似,集合中的元素是按顺序进行存放和处理的,所以我们可以像访问数组元素一样通过序号访问和处理List集合元素。而且List集合重点关注的是索引不是元素本身,因此集合中允许出现重复元素。
实现List集合接口的常用类有ArrayList、LinkedList、Vector和Stack。其中,ArrayList数组列表,是一种功能强大、应用广泛的集合类型,它采用数组结构来存储数据。
LinkedList与ArrayList在实现原理上完全不同,它是采用双向链表实现数据存储,每个数组元素都存储在一个节点容器中。这个节点除了保存元素对象以外,还保存了指向链表中前一个和后一个节点的指针,这样所有节点连接成一条链。当按序号索引数据时需要逐一进行向前或向后遍历,所以查询速度慢,但是插入数据时只需要记录本项的前后项即可,所以插入、删除速度较快
Vector与ArrayList实现原理相同,但其使用了synchronized方法来保证线程安全,所以性能上比ArrayList要差。Stack继承自Vector,其通过5个操作对Vector进行了扩展,实现了后进先出的堆栈结构。这5个操作分别是通常的push和pop方法,取堆栈顶点的peek方法,判断堆栈是否为空empty方法和在堆栈中查找项并确定到堆栈顶距离的searc方法。
在本节主要介绍ArrayList和LinkedList。Vector已不建议使用,而Stack也已有了更好的替代集合类Deque,所以这两个集合在这里不做进一步介绍。感兴趣的读者请阅读相关文档。
1 List 接口的定义
public interface List<E> extends Collection<E>
List是有序集合,用户可以准确地控制元素在集合中的插入位置,可以通过序号获得集合中的元素,可以通过元素获得元素在集合中的位置。它的主要方法如表。
练习题:
1.1 List 集合的三种遍历方式
练习题:
public static void main(String[] args) {
List list = new ArrayList<>();
list.add(new Book("红楼梦33", 35.2, "曹雪芹"));
list.add(new Book("红楼梦22", 15.7, "Tom"));
list.add(new Book("红楼梦11", 45.4, "Juy"));
for (Object a : list) {
System.out.println(a);
}
}
class Book{
String name;
double price;
String author;
public Book(String name, double price, String author) {
this.name = name;
this.price = price;
this.author = author;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public double getPrice() {
return price;
}
public void setPrice(double price) {
this.price = price;
}
public String getAuthor() {
return author;
}
public void setAuthor(String author) {
this.author = author;
}
@Override
public String toString() {
return "name='" + name +
"\t\tprice=" + price +
"\t\t author='" + author;
}
}
测试结果为:
2、ArrayList 集合
ArrayList是Java提供的一种具有可变大小的动态数组结构,其可以像数组一样提供快速的随机元素访问特性,索引地址也是从0开始。但与数组不同的是,ArayList可以根据需要动态地进行空间的分配和调整,让用户很方便地添加和删除元素,从而大大增强了数组的灵活性。
ArrayList分配的空间大于实际存储的数据个数,这样便于添加和插入元素。但插入、删除元素需要进行数据移动等内存操作,所以进行查询、索引等操作速度快,而插入、删除等操作速度慢。
2.1ArrayList类的定义形式:
public class ArrayList<E> extends Abstract List<E> implements List<E>
, RandomAccess,Cloneable,Serializable
ArrayList实现了List接口,故其常用方法与List接口相一致,如表8-2所列。ArrayList类的构造方法有3种,如表8-3所列。
ArrayList 可以加入null,并目多个。
例题:已知一个人员名单,请将每个名字前面添加一个序号
public static void main(String[] args) {
//创建一个ArrayList 数组
ArrayList<String> roster =new ArrayList<String>();
//将名字逐一加入
roster.add("zhang");
roster.add("wang");
roster.add("li");
roster.add("zhao");
System.out.print("roster=");
//通过逐一获取名字,显示名单内容
for (int i = 0; i < roster.size() ; i++) {
System.out.print(roster.get(i)+" ");
}
System.out.println();
//直接显示动态数组内容
int number=1;
for (int i = 0; i < roster.size() ; i+=2) {
roster.add(i,String.valueOf(number));
number++;
}
//输出新数组内容
System.out.println("new roster ="+roster);
//删除第一个人的序号
roster.remove(0);
//删除第一个人的姓名
roster.remove(0);
//输出数组内容
System.out.println("remove one person ,roster ="+roster);
}
测试结果为:
【程序说明】
本段程序包含三个部 分:
第一部分创建人员名单数组,第二部分插入人员序号,第三部分删除第一个人的相关信息。这3部分实现了动态数组的添加、查询、插入、删除等基本操作,其与普通数组不同之处在于如下几点。
(1)相关操作均是调用了对应方法完成,所以从效率上要低一些。
(2)添加、插入、删除操作对原有数组顺序不会产生影响,其会自动完成原有数据的移动。
(3)进行插入、删除操作时要把数组元素的自动移动考虑进去。
(4)输出整个数组内容时可以直接使用ArrayList对象,而不需要逐一遍历。这是因为ArrayList类重写了toString0方法。
在这段程序中有两个地方需要初学者重点关注。
一个是第二部分的序号插入操作中步长为2而不是1。这时因为在当前名字位置插入一个序号后,原有位置的名字会自动后移,而i+1操作又把指针指向了这个名字,于是又在这个名字前面插入了一个序号,执行结果就是插入的序号都在一个名字的前面。而且随着序号的不断插入,数组长度持续增加,i值永远小于数组长度,从而变成一个死循环。
另一个是第三部分的删除操作,使用的是两个remove(0),而不是remove(0)remove(1)。道理与上面类似,当完成第一个remove(0)时,后面的数据自动向前移动,原来的第1个位置数据移动到了第0个位置,执行remove(1)删除的就是原来第2个位置的数据。
【进一步讨论】
前面提到,AmrayList内部采用的仍然是数组结构,而数组的内存空间的地址是连续的,如果当前的空间用尽,如何动态扩展呢?答案是重新分配一个更大的连续地址空间,将当前数组的数据复制到新的空间中,然后将对象引用指面新空间的首地址。
3、Vector 集合
4、LinkedList 集合类
LinkedList集合类是另一个常用的线性列表集合。但是其内部结构与ArrayList完全不同,它采用的是双向链表结构,这使得在完成插入、删除操作时非常快捷方便。然而在完成遍历查询时由于无法保存上一次的查询位置,使得实现查询操作效率低下。
4.1 LinkedList类的定义形式:
public class LinkedList<E> extends AbstractSequentialList<E> implements List<E>,Deque<E>,Cloneable,Serializable
LinkedList实现了两个接口:List和Deque接口,List接口中定义了线性表操作方法Deque接口中定义了线性数列从队列两端访问元素的方法,因此LinkedList对象既可以表示线性序列表,也可以把它当作堆栈使用,还可以把它当作队列使用。
LinkedList主要方法如表8-4所列。
public static void main(String[] args) {
Node jack=new Node("jack");
Node tom=new Node("tom");
Node Bob=new Node("Bob");
//连接三个结点,形成双链表
// jack-->tom-->Bob
jack.next=tom;
tom.next= Bob;
//Bob-->tom-->jack
Bob.pre=tom;
tom.pre=jack;
//让first 引用指向Jack ,这就是双链表都头结点
Node first =jack;
//尾结点指向Bob
Node last=Bob;
System.out.println("==========添加后==========");
//在tom 和 Bob 之间加一个 张三
Node zs=new Node("张三");
zs.next=Bob;
zs.pre=tom;
tom.next=zs;
Bob.pre=zs;
//遍历双链表
while (true){
if (first==null){ //空链表
break;
}
System.out.println(first);
first=first.next;
}
}
//定义一个Node 类,Node 对象 表示双链表的一个结点
class Node{
public Object item; //真正存放数据
public Node next; //指向后一个结点
public Node pre; //指向前一个结点
public Node(Object name){
this.item=name;
}
public String toString(){
return "Node name = "+item;
}
}
测试结果:
添加元素源码:
删除元素源码:
二、Set 集合
我们学习了List列表集合,这种集合的优点是实现了元素的有序存储和访问,但也存在一个很重要的问题:查找执行的效率不高。如果想在列表中查找特定元素需要逐个比较所有元素看是否满足条件,一旦列表很大就要花费很长时间。而且列表不限制重复元素的添加,因此要想创建一个没有重复元素的列表,就需要在添加新元素之前逐个检查列表中的所有元素,判断该元素是否已在列表中。
为了解决这一问题,我们提供了另一种集合:Set集合这种集合与数学概念上的集合很接近,即存储的元素是唯一的和无序的,所以我们也将Set集合称为数学集合。Set集合在快速查找元素和防止重复元素上有着明显优势。
Set接口定义了数学集合的相关操作,而具体实现Set接口的类有两个:HashSet和TreeSet。HashSet是一个通用的Set集合类,而TreeSet在功能上进行了一些扩展。
Set接口的定义形式:
public interface Set<E> extends Collection<E>
Set接口的常用方法
和List接口一样,Set接口也是Collection的子接口,因此,常用方法和Collection接口一样
Set接口继承了Collection接口,因此提供了Collection接口定义的所有操作,如add、contains和remove。并且Set的这些操作效率更高,即使添加很多元素,查找都能够很快完成。Set接口提供的方法与Collection几乎完全相同,因此参考表8-1即可。但Set能利用相关方法实现数学上的集合运算,如交集、并集、差集等。运算的方法如表8-5所列。
表8-5常用的集合运算方法说明
①无序 (添加和取出的顺序不一致)
②不允许重复元素,所以最多包含一个null
Set接口的遍历方式:
同Collection的遍历方式一样,因为Set接口是Collection接口的子接口
1.可以使用迭代器
2.增强for
3.不能使用索引的方式来获取
1、HashSet 集合类
HashSet的定义形式:
public class HashSet<E> extends AbstractSet<E> implements Set<E>,Cloneable,Serializable
该类实现了Set接口,为用户提供快速查找和添加元素的功能。其主要的方法如下表
hash值一样就会成为链表,重写hash方法
在使用构造方法创建空HashSet时,会预先分配一些元素空间,默认是16个元素。加载因子是指当空间利用率达到这个因子时就需要进行扩容了。
临界因子:16*0.75=12,当添加元素到达12时就会扩容;在链表中,链表长度为8时,会扩容集合,当集合扩容到64则树化为红黑树
HashSet的内部数据结构采用的是HashMap,其结构特征下一节我们会详细介绍,这里我们只需要知道,HashSet存储每个元素时都会生成一个唯一的整数标识散列码hashcode),与其关联。HashSet根据这个标识来决定元素所在的存储位置。HashSet之所以能快速实现元素查询也据此有关。
但HashSet也有一个很明显的缺点,那就是保存的元素没有任何特定的顺序,既不按字母顺序排列也不按添加顺序进行排列(Java后来提供了一种新的HashSet类——LinkedHashSet类,该类通过一个链表来维护元素添加的顺序,其所有方法均与HashSet相一致,所以这里不做赘述)。
因此,在Set中插入、删除和定位元素,HashSet是最好的选择。
1.1 LinkedHashSet
(1) LinkedHashSet 是 HashSet 的子类
(2) LinkedHashSet 底层是一个 LinkedHashMap,底层维护了一个数组+ 双向链表
(3) LinkedHashSet 根据元素的 hashCode 值来决定元素的存储位置,同时使用链表维护元素的次序,这使得元素看起来是以插入顺序保存的.
(4) LinkedHashSet 不允许添重复元素
2、TreeSet 集合类
TreeSet是实现Set接口的另一个集合类,但其与HashSet不同,TreeSet内部采用了二叉搜索树的数据结构进行元素的存储,所以TreeSet相比HashSet而言实现了元素的有序存放,这是两者的最大不同之处。
TreeSet同样能快速地进行元素的添加、删除、查找操作,但相对于HashSet还是要慢一些。因此TreeSet适合按顺序遍历。需要强调的是,使用TreeSet类有一个前提条件,那就是存储的元素类型必须是可排序的,例如String类或Integer类等。如果不支持排序的类实例加入到TreeSet中,运行时会产生异常,因为TreeSet类不知道如何对这些元素进行排序。所以遇到这类元素最好使用HashSet类进行存储。
TreeSet类的定义形式:
public class TreeSet<E> extends Abstract Set<E> implements NavigableSet<E>,Cloneable,Serializable
TreeSet类实现了NavigableSet接口,这个接口继承了Set接口,)能够使用元素的自然顺序对元素进行排序,或者根据创建集合时提供的Comparator(比较器)进行排序。TreeSe类除了实现,Set接口中的方法以外,还提供了一些跟元素顺序有关的方法。TreeSet类的部分方法如下:
底层源码分析:
练习题:
【例】有一个班级学生参加校运动会,现进行报名。已知各项比赛的报名人员,请统计班级参加比赛的人数。如果按姓名输出人员名单如何处理?
【分析】要实现人员统计不能简单进行人员叠加,还要将重复比赛的人数去掉,因此考虑使用set集合完成人员名单的存储。如果按序输出可以使用TreeSet集合。
import java.util.HashSet;
import java.util.TreeSet;
public class hashSet {
public static void main(String[] args) {
HashSet<String> race0 = new HashSet<String>();//创建第一个比赛名单
HashSet<String> race1 = new HashSet<String>();//创建第二个比赛名单
race0.add("wu"); //向第一个名单中添加人员,共4人
race0.add("wang");
race0.add("li");
race0.add("zhang");
race1.add("zhao");//向第二个名单中添加人员,共4人
race1.add("wang");
race1.add("li");
race1.add("liu");
HashSet race = new HashSet(race0);
race.addAll(race1);
//将两个名单合并
System.out.println("班级参赛人数为" + race.size() + "人");
TreeSet list = new TreeSet(race);//将人员名单放入TreeSet集合对象,自动完成排序
System.out.println("参赛人员:");
for (Object s : list) {//遍历输出人员名单
System.out.println(s);
}
}
}
运行结果为:
在本题目中,我们同时使用了HashSet集合和TreeSet集合。简单的元素添加HashSet比TreeSet效率要高,所以没有使用TreeSet。而TreeSet具备元素按序排列的特点,因此在按序输出时先把元素放到一个TreeSet中,让其自动完成排序,十分方便。
对于Set集合要注意的是,由于集合中元素不是顺序存储,所以无法像List列表一样通过序号访问,这时可以使用例题中的foreach循环进行遍历,也可以使用Iterator迭代器完成遍历。
三、Map 集合
Map集合是一类特殊的集合,其与前面介绍的集合不同,它存储的元素都是具有某种单向关联关系的对象对,因此我们也称这种集合为映射。
这种具有映射关系的对象对由键(key)和值(value)组成,一个键只能映射一个值,但允许多个不同的键映射到同一个值上。集合中的键必须是唯一的,而值允许重复。
映射在解决一些特定问题上非常方便,最常见的就是电话簿。我们要想查找一个人的电话号时,需要翻看电话簿,如果把电话簿放入Map集合中,按人名搜索就可以快速找到对应的电话号。这里的人名就是键,对应的电话号就是值。
Java中通过Map接口来定义这种集合。具体实现Map接口的类有两个:HashMap和TreeMap。这两个类的命名与Set中的两个类命名形式很相似,对应的内部实现也很相似。HashMap是一种比较通用的映射集合,而TreeMap能以有序的形式存放键值对元素。
1、Map 接口
Map接口是实现映射集合的根接口,其定义了关于映射集合的相关操作方法,常用方法如表8-9所列。
在这些方法中有一类方法需要注意,就是返回相关集合视图的方法,有entrySet():返回整个结果集的Set视图;keySet():返回包含所有键kev的Set视图;values():返回包含所有值value的Collection视图。
这3个结果集合对完成一些特定遍历非常方便,但这些结果集合并不是复制原结果集合内容,而是创建了一个集合视图,如果对视图中的内容做修改或删除操作会直接影响原结果集,因此完成这两种操作时要格外注意。
Map接口的具体实现类主要有两个:HashMap和TreeMap。与实现Set的两个类相似,如果在Map中插入、删除和定位元素,HashMap是最好的选择。但如果要按顺序遍历键,那么TreeMap会更好。下面就介绍一下这两个类。
1.1 Map 接口遍历方法
练习题:
2、HashMap 集合类
HashMap的定义形式:
public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>,Cloneable,Serializable
HashMap类是基于哈希表(hashtable)实现Map接口定义的。HashMap允许使用null值和null键。HashMap类的构造方法有4个,相关说明如表8-10所列。
2.1 HashMap 源码分析
3、Hashtable 集合类
4、TreeMap 集合类
TreeMap集合类的定义形式:
public class TreeMap<K,V> extends AbstractMap<K,V> implements NavigableMap<K,V>,Cloneable,Serializable
TreeMap类是对基于红黑树(Red-Blacktree)的NavigableMap接口的具体实现。该集合类根据键的自然顺序进行排序,或者根据构造集合对象时提供的Comparator进行排序。
TreeMap类除了实现Map接口的方法以外,也提供了一系列与顺序存储有关的方法。TreeMap的构造方法和常用方法如表8-11、表8-12所列。
5、Properties
如果有相同的key,则值会被替换
四、Collections 工具类
Collections类是Java为方便对Collection集合的操作和处理所提供的一个类。要注意Collections类与Collection接口的区别:Collection接口是一类集合的根接口,提供对这类集合中的元素进行基本操作的通用方法;而Collections类是具体的实现类为实现Collection接口的集合提供了一系列静态方法,完成元素的处理,如排序、查找、特定变换等操作。
Collections类的常用方法如表8-13所列。
在Collections类中的多个方法都要求集合元素是排好序的或是可排序的,那么如何让这些集合元素像字符串或整数一样具备排序条件呢?
String对象和数值对象之所以能够排序,是因为相关类实现了Comparable接口。这个接口中定义了一个compareTo()方法,用于给出两个对象比较大小的条件。String类和Integer等类都实现了Comparable接口的compareTo()方法,所以这些对象之间可以比较大小,也就可以进行排序。而如果我们想让一个类具备排序条件,就可以继承并实现Comparable接口的compareTo方法,这种排序称为类的自然排序。
除了在类的内部继承并重写Comparable接口的compareTo方法以外,Java另外提供了一个对象比较接口:Comparator接口。Comparator接口定义了compare)方法强行对某个对象collection进行整体排序。我们可以将Comparator传递给sort方法(如Collections.sort或Arrays.sort),从而允许在排序顺序上实现精确控制。还可以使用Comparator来控制某些数据结构(如有序set或有序映射)的顺序,或者为那些没有自然顺序的对象collection提供排序。
【例】有一个学生点名册,要求按学号进行排序。
public static void main(String[] args) {
Student st0 = new Student("1604010510", "zhang", 18, "male", "computer");
Student st1 = new Student("1604010501", "wang", 19, "male", "computer");
Student st2 = new Student("1503010201", "1i", 18, "male", "math");
Student st3 = new Student("1605010315", "zhao", 18, "female", "software");
Student st4 = new Student("1604010513", "liu", 17, "male", "computer");
ArrayList<Student> stu = new ArrayList<Student>();//创建学生列表
stu.add(st0);//向列表中添加学生对象
stu.add(st1);
stu.add(st2);
stu.add(st3);
stu.add(st4);
System.out.println("排序前名单:");
for (Student st : stu) {
st.display();
}
Collections.sort(stu);//内部定义排序顺序
Collections.sort(stu,new StudentComp());//外部定义排序顺序
System.out.println("排序后名单:");
for (Student st : stu) {
st.display();
}
}
class Student implements Comparable<Student> { //定义学生类,并实现Comparable接口,按学号排序
private String number;
private String name;
private int age;
private String sex;
private String major;
public Student(String number, String name, int age, String sex, String major) {
super();
this.number = number;
this.name = name;
this.age = age;
this.sex = sex;
this.major = major;
}
public String getNumber() {
return number;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public void display() {
System.out.printf("number:%s;name:%-6s;age:%d;major:%-8s\n", number, name, age, major);
}
public int compareTo(Student stu) {//重写接口方法
return (number.compareTo(stu.number));//按学号从小到大排序
}
}
class StudentComp implements Comparator<Student> {//继承并实现Comparator接口,来创建外部比较器,按年龄和姓名排序
public int compare(Student stu1, Student stu2) {
if (stu1.getAge() != stu2.getAge()) {
return (stu1.getAge() - stu2.getAge());
} else {
return (stu1.getNumber().compareTo(stu2.getNumber()));
}
}
}
测试结果为:
在本程序中,实现了排序的两种不同处理方法。一种是在类的内部重写Comparable接口的compareTo方法,该方法封装到了类的内部,表示该类是可排序的,实现这个接口的类就能用于Java中的各种有序集合。Java中的很多类,例如String、各种数值类等都实现了这个接口。这种方式比较固定,如果该类的排序方法不适合用户的需求,修改起来会比较麻烦。
另一种是在类的外部创建一个比较器类,继承并重写Comparator接口的compare方法,来实现该类对象的比较操作。这种方式更为灵活,可以根据需要定义各种不同的比较器,实现不同的排序操作。