
任务1 查询标题
关键步骤如下。
- 创建集合对象,并添加数据。
- 统计新闻标题总数量。
- 输出新闻标题名称。
1.1.1 认识集合
开发应用程序时,如果想存储多个同类型的数据,可以使用数组来实现;但是使用数组存在如下一些明显缺陷:
- 数组长度固定不变,不能很好地适应元素数量动态变化的情况。
- 可通过数组名.length获取数组的长度,却无法直接获取数组中实际存储的元素个数。
- 数组采用在内存中分配连续空间的存储方式存储,根据元素信息查找时效率比较低,需要多次比较。
从以上分析可以看出数组在处理一些问题时存在明显的缺陷,针对数组的缺陷,Java提供了比数组更灵活、更实用的集合框架,可大大提高软件的开发效率,并且不同的集合可适用于不同应用场合。
Java集合框架提供了一套性能优良、使用方便的接口和类,它们都位于java.util包中,其主要内容及彼此之间的关系如图1.1所示。

图1.1 Java集合框架图
从图1.1中可以看出,Java的集合类主要由Map接口和Collection接口派生而来,其中Collection接口有两个常用的子接口,即List接口和Set接口,所以通常说Java集合框架由3大类接口构成(Map接口、List接口和Set接口)。本章讲解的主要内容就是围绕这3大类接口进行的。
注意
虚线框表示接口或者抽象类,实线框表示开发中常用的实现类。
1.1.2 List接口
Collection接口是最基本的集合接口,可以存储一组不唯一、无序的对象。List接口继承自Collection接口,是有序集合。用户可使用索引访问List接口中的元素,类似于数组。List接口中允许存放重复元素,也就是说List可以存储一组不唯一、有序的对象。
List接口常用的实现类有ArrayList和LinkedList。
1. 使用ArrayList类动态存储数据
针对数组的一些缺陷,Java集合框架提供了ArrayList集合类,对数组进行了封装,实现了长度可变的数组,而且和数组采用相同的存储方式,在内存中分配连续的空间,如图1.2所示,所以,经常称ArrayList为动态数组。但是它不等同于数组,ArrayList集合中可以添加任何类型的数据,并且添加的数据都将转换成Object类型,而在数组中只能添加同一数据类型的数据。

图1.2 ArrayList存储方式示意图
ArrayList类提供了很多方法用于操作数据,如表1-1中列出的是ArrayList类的常用方法。
表1-1 ArrayList类的常用方法

示例1
使用ArrayList常用方法动态操作数据。
实现步骤如下。
(1)导入ArrayList类。
(2)创建ArrayList对象,并添加数据。
(3)判断集合中是否包含某元素。
(4)移除索引为0的元素。
(5)把索引为1的元素替换为其他元素。
(6)输出某个元素所在的索引位置。
(7)清空ArrayList集合中的数据。
(8)判断ArrayList集合中是否包含数据。
关键代码:

在示例1中,① 的代码调用ArrayList的无参构造方法,创建集合对象。常用的ArrayList类的构造方法还有一个带参数的重载版本,即ArrayList(int initialCapacity),它构造一个具有指定初始容量的空列表。
② 的代码将list集合中索引为0的元素删除,list集合的下标是从0开始,也就是删除了“张三丰”,集合中现有元素为“郭靖”和“杨过”。
③ 的代码将list集合中索引为1的元素替换为“黄蓉”,即将“杨过”替换为“黄蓉”,集合中现有元素为“郭靖”和“黄蓉”。
④ 的代码是使用for循环遍历集合,输出集合中的所有元素。list.get(i)取出集合中索引为i的元素,并强制转换为String类型。
⑤ 的代码为输出元素“小龙女”所在的索引位置,因集合中没有该元素,所以输出结果为-1。
⑥ 的代码是使用增强for循环遍历集合,输出集合中的所有元素。增强for循环的语法在Java基础课程中讲过,这里不再赘述。可以看出,遍历集合时使用增强for循环比普通for循环在写法上更加简单方便,而且不用考虑下标越界的问题。
⑦ 的代码用来判断list集合是否为空,因为前面执行了list.clear()操作,所以集合已经为空,输出为true。
注意
① 调用ArrayList类的add(Object obj)方法时,添加到集合当中的数据将被转换为Object类型。
② 使用ArrayList类之前,需要导入相应的接口和类,代码如下:
import java.util.ArrayList; import java.util.List;
示例2
使用ArrayList集合存储新闻标题信息(包含ID、名称、创建者),输出新闻标题的总数量及每条新闻标题的名称。
实现步骤如下。
(1)创建ArrayList对象,并添加数据。
(2)获取新闻标题的总数。
(3)遍历集合对象,输出新闻标题名称。
关键代码:
//创建新闻标题对象,NewTitle为新闻标题类 NewTitle car=new NewTitle(1, "汽车", "管理员"); NewTitle test=new NewTitle(2, "高考", "管理员"); //创建存储新闻标题的集合对象 List newsTitleList=new ArrayList(); //按照顺序依次添加新闻标题 newsTitleList.add(car); newsTitleList.add(test); //获取新闻标题的总数 System.out.println("新闻标题数目为:"+newsTitleList.size()+"条"); //遍历集合对象 System.out.println("新闻标题名称为:"); for(Object obj:newsTitleList){ NewTitle title=(NewTitle)obj; System.out.println(title.getTitleName()); }
输出结果如图1.3所示。

图1.3 输出新闻标题信息
在示例2中,ArrayList集合中存储的是新闻标题对象。在ArrayList集合中可以存储任何类型的对象。其中,代码List newsTitleList=new ArrayList();是将接口List的引用指向实现类ArrayList的对象。在编程中将接口的引用指向实现类的对象是Java实现多态的一种形式,也是软件开发中实现低耦合的方式之一,这样的用法可以大大提高程序的灵活性。随着编程经验的积累,开发者对这个用法的理解会逐步加深。
ArrayList集合因为可以使用索引来直接获取元素,所以其优点是遍历元素和随机访问元素的效率比较高。但是由于ArrayList集合采用了和数组相同的存储方式,在内存中分配连续的空间,因此在添加和删除非尾部元素时会导致后面所有元素的移动,这就造成在插入、删除等操作频繁的应用场景下使用ArrayList会导致性能低下。所以数据操作频繁时,最好使用LinkedList存储数据。
2. 使用LinkedList类动态存储数据
LinkedList类是List接口的链接列表实现类。它支持实现所有List接口可选的列表的操作,并且允许元素值是任何数据,包括null。
LinkedList类采用链表存储方式存储数据,如图1.4所示,优点在于插入、删除元素时效率比较高,但是LinkedList类的查找效率很低。

图1.4 LinkedList类存储示意图
它除了包含ArrayList类所包含的方法外,还提供了表1-2所示的一些方法,可以在LinkedList类的首部或尾部进行插入、删除操作。
表1-2 LinkedList类的常用方法

示例3
使用LinkedList集合存储新闻标题(包含ID、名称、创建者),实现获取、添加及删除头条和末条新闻标题信息功能,并遍历集合。
实现步骤如下。
(1)创建LinkedList对象,并添加数据。
(2)添加头条和末条新闻标题。
(3)获取头条和末条新闻标题信息。
(4)删除头条和末条新闻标题。
关键代码:
//创建多个新闻标题对象 NewTitle car=new NewTitle(1, "汽车", "管理员"); NewTitle medical=new NewTitle(2, "医学", "管理员"); NewTitle fun=new NewTitle(3, "娱乐", "管理员"); NewTitle gym=new NewTitle(4, "体育", "管理员"); //创建存储新闻标题的集合对象并添加数据 LinkedList newsTitleList=new LinkedList(); newsTitleList.add(car); newsTitleList.add(medical); //添加头条新闻标题和末条新闻标题 newsTitleList.addFirst(fun); newsTitleList.addLast(gym); System.out.println("头条和末条新闻已添加"); //获取头条以及最末条新闻标题 NewTitle first=(NewTitle) newsTitleList.getFirst(); System.out.println("头条的新闻标题为:"+first.getTitleName()); NewTitle last=(NewTitle) newsTitleList.getLast(); System.out.println("排在最后的新闻标题为:"+last.getTitleName()); //删除头条和末条新闻标题 newsTitleList.removeFirst(); newsTitleList.removeLast(); System.out.println("头条和末条新闻已删除"); System.out.println("遍历所有新闻标题:"); for(Object obj:newsTitleList){ NewTitle newTitle=(NewTitle)obj; System.out.println("新闻标题名称:"+newTitle.getTitleName()); }
输出结果如图1.5所示。

图1.5 使用LinkedList存储并操作新闻标题信息
除了表1-2中列出的LinkedList类提供的方法外,LinkedList类和ArrayList类所包含的大部分方法是完全一样的,这主要是因为它们都是List接口的实现类。由于ArrayList采用和数组一样的连续的顺序存储方式,当对数据频繁检索时效率较高,而LinkedList类采用链表存储方式,当对数据添加、删除或修改比较多时,建议选择LinkedList类存储数据。
1.1.3 Set接口
1. Set接口概述
Set接口是Collection接口的另外一个常用子接口,Set接口描述的是一种比较简单的集合。集合中的对象并不按特定的方式排序,并且不能保存重复的对象,也就是说Set接口可以存储一组唯一、无序的对象。
Set接口常用的实现类有HashSet。
2. 使用HashSet类动态存储数据
假如现在需要在很多数据中查找某个数据,LinkedList类就无需考虑了,它的数据结构决定了它的查找效率低下。如果使用ArrayList类,在不知道数据的索引且需要全部遍历的情况下,效率一样很低下。为此Java集合框架提供了一个查找效率高的集合类HashSet。HashSet类实现了Set接口,是使用Set集合时最常用的一个实现类。HashSet集合的特点如下。
- 集合内的元素是无序排列的。
- HashSet类是非线程安全的。
- 允许集合元素值为null。
表1-3中列举了HashSet类的常用方法。
表1-3 HashSet类的常用方法

示例4
使用HashSet类的常用方法存储并操作新闻标题信息,并遍历集合。
实现步骤如下。
(1)创建HashSet对象,并添加数据。
(2)获取新闻标题的总数。
(3)判断集合中是否包含汽车新闻标题。
(4)移除对象。
(5)判断集合是否为空。
(6)遍历集合。
关键代码:
//创建多个新闻标题对象 NewTitle car=new NewTitle(1, "汽车", "管理员"); NewTitle test=new NewTitle(2, "高考", "管理员"); //创建存储新闻标题的集合对象 Set newsTitleList=new HashSet(); //按照顺序依次添加新闻标题 newsTitleList.add(car); newsTitleList.add(test); //获取新闻标题的总数 System.out.println("新闻标题数目为:"+newsTitleList.size()+"条"); //判断集合中是否包含汽车新闻标题 System.out.println("汽车新闻是否存在:"+newsTitleList.contains(car)); //输出true newsTitleList.remove(test); //移除对象 System.out.println("汽车对象已删除"); System.out.println("集合是否为空:"+newsTitleList.isEmpty()); //判断是否为空 //遍历所有新闻标题 System.out.println("遍历所有新闻标题:"); for(Object obj:newsTitleList){ NewTitle title=(NewTitle)obj; System.out.println(title.getTitleName()); }
输出结果如图1.6所示。

图1.6 使用HashSet类存储并操作新闻标题信息
注意
使用HashSet类之前,需要导入相应的接口和类,代码如下:
import java.util.Set; import java.util.HashSet;
在示例4中,通过增强for循环遍历HashSet,前面讲过List接口可以使用for循环和增强for循环两种方式遍历。使用for循环遍历时,通过get()方法取出每个对象,但HashSet类不存在get()方法,所以Set接口无法使用普通for循环遍历。其实遍历集合还有一种比较常用的方式,即使用Iterator接口。
1.1.4 Iterator接口
1. Iterator接口概述
Iterator接口表示对集合进行迭代的迭代器。Iterator接口为集合而生,专门实现集合的遍历。此接口主要有如下两个方法:
- hasNext():判断是否存在下一个可访问的元素,如果仍有元素可以迭代,则返回true。
- next():返回要访问的下一个元素。
凡是由Collection接口派生而来的接口或者类,都实现了iterate()方法,iterate()方法返回一个Iterator对象。
2. 使用Iterator遍历集合
下面通过示例来学习使用迭代器Iterator遍历Arraylist集合。
示例5
使用Iterator接口遍历ArrayList集合。
实现步骤如下。
(1)导入Iterator接口。
(2)使用集合的iterate()方法返回Iterator对象。
(3)while循环遍历。
(4)使用Iterator的hasNext()方法判断是否存在下一个可访问的元素。
(5)使用Iterator的next()方法返回要访问的下一个元素。
关键代码:
public static void main(String[] args){ ArrayList list=new ArrayList(); list.add("张三"); list.add("李四"); list.add("王五"); list.add(2, "杰伦"); System.out.println("使用Iterator遍历,分别是:"); Iterator it=list.iterator(); //获取集合迭代器Iterator while(it.hasNext()){ //通过迭代器依次输出集合中所有元素的信息 String name=(String)it.next(); System.out.println(name); } }
输出结果:
使用Iterator遍历,分别是: 张三 李四 杰伦 王五
示例5中是以ArrayList为例使用Iterator接口,其他由Collection接口直接或间接派生的集合类,如已经学习的LinkedList、HashSet等,同样可以使用Iterator接口进行遍历,遍历方式与示例5遍历ArrayList集合的方式相同。例如,将示例5改为使用Iterator对象遍历,关键代码如下。
//使用Iterator遍历HashSet集合 while(iterator.hasNext()){ NewTitle title=(NewTitle) iterator.next(); System.out.println(title.getTitleName()); }
1.1.5 Map接口
1. Map接口概述
Map接口存储一组成对的键(key)——值(value)对象,提供key到value的映射,通过key来检索。Map接口中的key不要求有序,不允许重复。value同样不要求有序,但允许重复。表1-4中列举了Map接口的常用方法。
表1-4 Map接口的常用方法

Map接口中存储的数据都是键——值对,例如,一个身份证号码对应一个人,其中身份证号码就是key,与此号码对应的人就是value。
2. 使用HashMap类动态存储数据
最常用的Map实现类是HashMap,其优点是查询指定元素效率高。
示例6
使用HashMap类存储学生信息,要求可以根据英文名检索学生信息。
实现步骤如下。
(1)导入HashMap类。
(2)创建HashMap对象。
(3)调用HashMap对象的put()方法,向集合中添加数据。
(4)输出学员个数。
(5)输出键集。
(6)判断是否存在“Jack”这个键,如果存在,则根据键获取相应的值。
(7)判断是否存在“Rose”这个键,如果存在,则根据键获取相应的值。
关键代码:
//创建学员对象 Student student1=new Student("李明", "男"); Student student2=new Student("刘丽", "女"); //创建保存“键——值对”的集合对象 Map students=new HashMap(); //把英文名称与学员对象按照“键——值对”的方式存储在HashMap中 students.put("Jack", student1); students.put("Rose", student2); //输出学员个数 System.out.println("已添加"+students.size()+"个学员信息"); //输出键集 System.out.println("键集:"+students.keySet()); String key="Jack"; //判断是否存在“Jack”这个键,如果存在,则根据键获取相应的值 if(students.containsKey(key)){ Student student=(Student)students.get(key); System.out.println("英文名为"+key+"的学员姓名:"+student.getName()); } String key1="Rose"; //判断是否存在“Rose”这个键,如果存在,则删除此键——值对 if(students.containsKey(key1)){ students.remove(key1); System.out.println("学员"+key1+"的信息已删除"); }
输出结果如图1.7所示。

图1.7 使用HashMap类存储并检索学生信息
注意
① 数据添加到HashMap集合后,所有数据的数据类型将转换为Object类型,所以从其中获取数据时需要进行强制类型转换。
② HashMap类不保证映射的顺序,特别是不保证顺序恒久不变。
遍历HashMap集合时可以遍历键集和值集。
示例7
改进示例6,遍历所有学员的英文名及学员详细信息。
实现步骤如下。
(1)遍历键集。
(2)遍历值集。
关键代码:
//创建学员对象 Student student1=new Student("李明", "男"); Student student2=new Student("刘丽", "女"); //创建保存“键——值对”的集合对象 Map students=new HashMap(); //把英文名称与学员对象按照“键——值对”的方式存储在HashMap中 students.put("Jack", student1); students.put("Rose", student2); //输出英文名 System.out.println("学生英文名:"); for(Object key:students.keySet()){ System.out.println(key.toString()); } //输出学生详细信息 System.out.println("学生详细信息:"); for(Object value:students.values()){ Student student=(Student)value; System.out.println("姓名:"+student.getName()+",性别:"+student.getSex()); }
输出结果如图1.8所示。

图1.8 使用HashMap集合遍历学生英文名及详细信息
在示例7中,使用增强for循环遍历HashMap集合的键集和值集,当然也可以使用前面的普通for循环或者迭代器Iterator来遍历,视个人习惯而选择。

Map补充案例
1.1.6 Collections类
Collections类是Java提供的一个集合操作工具类,它包含了大量的静态方法,用于实现对集合元素的排序、查找和替换等操作。
注意
Collections和Collection是不同的,前者是集合的操作类,后者是集合接口。
1. 对集合元素排序与查找
排序是针对集合的一个常见需求。要排序就要知道两个元素哪个大哪个小。在Java中,如果想实现一个类的对象之间比较大小,那么这个类就要实现Comparable接口。此接口强行对实现它的每个类的对象进行整体排序。这种排序被称为类的自然排序,类的compareTo()方法被称为它的自然比较方法。此方法用于比较此对象与指定对象的顺序,如果该对象小于、等于或大于指定对象,则分别返回负整数、零或正整数。
compareTo()方法的定义语法格式如下。
int compareTo(Object obj);
其中:
- 参数:obj即要比较的对象;
- 返回值:负整数、零或正整数,根据此对象是小于、等于还是大于指定对象返回不同的值。
实现此接口的对象列表(和数组)可以通过Collections.sort()方法(和Arrays.sort()方法)进行自动排序。示例8通过实现Comparable接口对集合进行排序。
示例8
学生类Student实现了Comparable接口,重写了compareTo()方法,通过比较学号实现对象之间的大小比较。
实现步骤如下。
(1)创建Student类。
(2)添加属性学号number(int)、姓名name(String)和性别gender(String)。
(3)实现Comparable接口、compareTo()方法。
关键代码:
public class Student implements Comparable{ private int number=0; //学号 private String name=""; //姓名 private String gender=""; //性别 public int getNumber(){ return number; } public void setNumber(int number){ this.number=number; } public String getName(){ return name; } public void setName(String name){ this.name=name; } public String getGender(){ return gender; } public void setGender(String gender){ this.gender=gender; } public int compareTo(Object obj){ Student student=(Student)obj; //如果学号相同,那么两者就是相等的 if(this.number==student.number){ return 0; //如果这个学生的学号大于传入学生的学号 }else if(this.number>student.getNumber()){ return 1; //如果这个学生的学号小于传入学生的学号 }else{ return -1; } } }
元素之间可以比较大小之后,就可以使用Collections类的sort()方法对元素进行排序操作了。前面介绍过List接口和Map接口,Map接口本身是无序的,所以不能对Map接口做排序操作;但是List接口是有序的,所以可以对List接口进行排序。注意List接口中存放的元素,必须是实现了Comparable接口的元素才可以。
示例9
使用Collections类的静态方法sort()和binarySearch()对List集合进行排序与查找。
实现步骤如下。
(1)导入相关类。
(2)初始化数据。
(3)遍历排序前集合并输出。
(4)使用Collections类的sort()方法排序。
(5)遍历排序后集合并输出。
(6)查找排序后某元素的索引。
关键代码:
//省略声明Student对象代码 public static void main(String[] args){ Student student1=new Student(); student1.setNumber(5); Student student2=new Student(); student2.setNumber(2); Student student3=new Student(); student3.setNumber(1); Student student4=new Student(); student4.setNumber(4); ArrayList list=new ArrayList(); list.add(student1); list.add(student2); list.add(student3); list.add(student4); System.out.println("-------排序前-------"); Iterator iterator=list.iterator(); while(iterator.hasNext()){ Student stu=(Student)iterator.next(); System.out.println(stu.getNumber()); } //使用Collections类的sort()方法对List集合进行排序 System.out.println("-------排序后-------"); Collections.sort(list); iterator=list.iterator(); while(iterator.hasNext()){ Student stu=(Student)iterator.next(); System.out.println(stu.getNumber()); } //使用Collections类的binarySearch()方法对List集合进行查找 int index=Collections.binarySearch(list,student3); //① System.out.println("student3的索引是:"+index); }
输出结果:
-------排序前------- 5 2 1 4 -------排序后------- 1 2 4 5 student3的索引是:0
示例9中,①的代码是使用Collections类的binarySearch()方法对List集合进行查找,因为student3的学号为1,故排序后索引变为0。
2. 替换集合元素
若有一个需求,需要把一个List集合中的所有元素都替换为相同的元素,则可以使用Collections类的静态方法fill()来实现。下面通过一个示例来学习使用fill()方法替换元素。
示例10
使用Collections类的静态方法fill()替换List集合中的所有元素为相同的元素。
实现步骤如下。
(1)导入相关类,初始化数据。
(2)使用Collections类的fill()方法替换集合中的元素。
(3)遍历输出替换后的集合。
关键代码:
public static void main(String[] args){ ArrayList list=new ArrayList(); list.add("张三丰"); list.add("杨过"); list.add("郭靖"); Collections.fill(list, "东方不败"); //替换元素 Iterator iterator=list.iterator(); while(iterator.hasNext()){ String name=(String)iterator.next(); System.out.println(name); } }
输出结果:
东方不败 东方不败 东方不败
至此,任务1已经全部完成。