文章目录
零、本讲学习目标
1、了解集合的概念和分类
2、了解Collection接口和List接口中的常用方法
3、掌握ArrayList集合和LinkedList集合的使用
4、会用Iterator与foreach循环
一、集合概述
1、引入集合的必要性
- 数组局限性:数组可以保存多个对象,但在无法确定需要保存多少个对象时,数组将不再适用,因为数组的长度不可变。
- 可能的场景:倘若要保存一个学校的学生信息,由于不停有新生来报道,同时也有学员毕业离开学校,这时学生的数目无法固定,并且随时可能变动。
- 解决的方法:为了保存这些数目不确定的对象,Java中提供了集合,集合可以存储任意类型的对象,并且长度可变。
2、集合的定义
Java中的集合就像一个容器,专门用来存储Java对象。集合对象可以是任意的数据类型,并且长度可变。其中,这些集合类都位于java.util
包中,在使用时一定要注意导包的问题。
还记得我们在《Java讲课笔记08:数组》里讲利用Arrays类的sort()方法对数组进行降序排列的案例Example810吧。
这个案例使用集合类Collections的reverseOrder()方法作为Arrays类的sort()方法的第二个参数,用于实现对数组的降序排列。大家可以看看程序开头,就有导包的语句:
3、集合的分类
(1)单列集合Collection
- 单列集合根接口,用于存储一系列符合某种规则的元素。
- Collection集合有两个重要的子接口,分别是List和Set。
- List集合的特点是元素有序、可重复。该接口的主要实现类有ArrayList和LinkedList。
- Set集合的特点是元素无序并且不可重复。该接口的主要实现类有HashSet和TreeSet。
(2)双列集合Map
- 双列集合根接口,用于存储具有键(Key)、值(Value)映射关系的元素。
- Map集合中每个元素都包含一对键值,并且Key唯一,在使用Map集合时通过指定的Key找到对应的Value。
- Map接口的主要实现类有HashMap和TreeMap。
4、集合体系核心结构
说明:虚线框里填写的都是接口类型,实线框里填写的都是具体的实现类。
二、Collection接口
1、Collection接口概述
Collection是单列集合根接口,用于存储一系列符合某种规则的元素,它有两个重要的子接口,分别是List和Set。
2、Collection接口常用方法
方法声明 | 功能描述 |
---|---|
boolean add(Object o) | 向集合中添加一个元素 |
boolean addAll(Collection c) | 将指定集合c中的所有元素添加到该集合中 |
void clear() | 删除该集合中的所有元素 |
boolean remove(Object o) | 删除该集合中指定的元素 |
boolean removeAll(Collection c) | 删除该集合中包含指定集合c中的所有元素 |
boolean isEmpty() | 判断该集合是否为空 |
boolean contains(Object o) | 判断该集合中是否包含某个元素 |
boolean containsAll(Collection c) | 判断该集合中是否包含指定集合c中的所有元素 |
Iterator iterator() | 返回在该集合的元素上进行迭代的迭代器(Iterator),用于遍历该集合所有元素 |
int size() | 获取该集合元素个数 |
Stream stream() | 将集合源转换为有序元素的流对象(JDK 8新方法) |
三、List接口
1、List接口概述
(1)List接口定义
List接口继承自Collection接口,是单列集合的一个重要分支,习惯性地会将实现了List接口的对象称为List集合。
(2)List接口特点
- List集合中允许出现重复元素,所有的元素是以一种线性方式进行存储的,在程序中可以通过索引(类似于数组中的元素下标)来访问集合中的元素。
- List集合还有一个特点就是元素有序,即元素的存入顺序和取出顺序一致。
(3)List接口常用方法
方法声明 | 功能描述 |
---|---|
void add(int index,Object element) | 将元素element插入在List集合的指定索引位置 |
boolean addAll(int index,Collection c) | 将集合c包含的所有元素插入到List集合的指定索引位置 |
Object get(int index) | 返回集合索引index处的元素 |
Object remove(int index) | 删除index索引处的元素 |
Object set(int index, Object element) | 将索引index处元素替换成element元素,并将替换后的元素返回 |
int indexOf(Object o) | 返回对象o在List集合中首次出现的位置索引 |
int lastIndexOf(Object o) | 返回对象o在List集合中最后一次出现的位置索引 |
List subList(int fromIndex, int toIndex) | 返回从索引fromIndex(包括)到 toIndex(不包括)处所有元素集合组成的子集合 |
Object[] toArray() | 将集合元素转换为数组 |
default void sort(Comparator<? super E> c) | 根据指定的比较器规则对集合元素排序(JDK 8新方法) |
2、List接口常用实现类
(1)ArrayList集合
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合,其内部的数据存储结构是数组形式。
(2)LinkedList集合
LinkedList是List接口的另一个实现类,其内部包含有两个Node类型的first和last属性的双向循环链表结构。
(3)Vector集合
Vector 类实现了一个动态数组。和 ArrayList 很相似,但是两者是不同的:
- Vector 是同步访问的
- Vector 包含了许多传统的方法,这些方法不属于集合框架
四、ArrayList集合
1、ArrayList集合定义
ArrayList是List接口的一个实现类,它是程序中最常见的一种集合,其内部的数据存储结构是数组形式。
2、ArrayList集合特点
- 由于ArrayList的存储结构,在增加或删除指定位置的元素时,会创建新的数组,效率比较低,因此不适合做大量的增删操作。
- 这种数组结构允许程序通过索引的方式来访问元素,使用ArrayList集合在遍历和查找元素时显得非常高效。
3、案例演示:创建与操作城市数组列表
- 创建Example2101
package net.hw.lesson21;
import java.util.ArrayList;
/**
* 功能:创建与操作城市数组列表
* 作者:华卫
* 日期:2020年05月22日
*/
public class Example2101 {
public static void main(String[] args) {
// 创建城市数组列表对象
ArrayList cities = new ArrayList();
// 判断数组列表是否为空
System.out.println("cities.isEmpty() = " + cities.isEmpty());
// 向数组列表里添加元素
cities.add("北京");
cities.add("上海");
cities.add("深圳");
cities.add(2, "广州"); // 广州在列表第3个位置
cities.add("泸州");
// 输出整个数组列表
System.out.println("cities = " + cities);
// 获取数组列表的长度
System.out.println("cities.size() = " + cities.size());
// 采用for循环遍历数组列表
for (int i = 0; i < cities.size(); i++) {
System.out.println("cities[" + i + "] = " + cities.get(i));
}
// 修改指定元素,比如将第5个元素改成“南京”
cities.set(4, "南京");
// 输出整个数组列表
System.out.println("cities = " + cities);
// 元素在列表的位置
String city = "深圳";
System.out.println("[" + city + "]在列表中的下标 = " + cities.indexOf(city));
// 按下标删除元素,比如删除下标为2的元素
cities.remove(2);
System.out.print("删除列表中下标为2的元素:");
// 输出整个数组列表
System.out.println("cities = " + cities);
// 按对象删除元素,比如删除“南京”
cities.remove("南京");
System.out.print("删除列表中的[南京]元素:");
// 输出整个数组列表
System.out.println("cities = " + cities);
// 判断数组列表是否为空
System.out.println("cities.isEmpty() = " + cities.isEmpty());
}
}
- 运行程序,查看结果
4、简要说明
- 必须导包:import java.util.ArrayList;
- 定义数组列表对象最好加上泛型:
ArrayList<String> cities = new ArrayList<>();
表名数组列表里的每个元素都是字符串类型,这样可以杜绝安全隐患 - 数组列表添加元素有两个重载的方法
(1)public boolean add(E e):默认在列表末尾添加新元素
(2)public void add(int index, E element):在列表指定位置添加新元素 - 设置或获取数组列表元素,传入的下标不存在,会抛出下标越界异常
- 可以采用foreach循环遍历数组列表
5、课堂练习:创建与操作成绩数组列表
- 创建成绩数组列表scores,泛型为Integer
- 添加8个成绩:67、89、90、35、54、80、69、86
- 采用for循环与foreach循环遍历成绩数组列表
- 将成绩数组列表排序输出(利用sort()方法,可参看第08讲的Example810)
五、LinkedList集合
1、LinkedList集合定义
LinkedList是List接口的另一个实现类,其内部包含有两个Node类型的first和last属性的双向循环链表结构。
2、LinkedList集合特点
- 由于LinkedList的存储结构, 对于元素的遍历和查找效率较低。
- LinkedList集合对于元素的增删操作表现出很高的效率。
3、双向循环链表结构
- 左图为新增元素,图中的元素1和元素2在集合中彼此为前后关系,在它们之间新增一个元素时,只需要让元素1记住它后面的元素是新元素,让元素2记住它前面的元素为新元素就可以了。
- 右图为删除元素,要想删除元素1和元素2之间的元素3,只需要让元素1与元素2变成前后关系就可以了。
4、LinkedList的特有方法
方法声明 | 功能描述 |
---|---|
void add(int index, E element) | 在此列表中指定的位置插入指定的元素。 |
void addFirst(Object o) | 将指定元素插入集合的开头 |
void addLast(Object o) | 将指定元素添加到集合的结尾 |
Object getFirst() | 返回集合的第一个元素 |
Object getLast() | 返回集合的最后一个元素 |
Object removeFirst() | 移除并返回集合的第一个元素 |
Object removeLast() | 移除并返回集合的最后一个元素 |
boolean offer(Object o) | 将指定元素添加到集合的结尾 |
boolean offerFirst(Object o) | 将指定元素添加到集合的开头 |
boolean offerLast(Object o) | 将指定元素添加到集合的结尾 |
Object peek() | 获取集合的第一个元素 |
Object peekFirst() | 获取集合的第一个元素 |
Object peekLast() | 获取集合的最后一个元素 |
Object poll() | 移除并返回集合的第一个元素 |
Object pollFirst() | 移除并返回集合的第一个元素 |
Object pollLast() | 移除并返回集合的最后一个元素 |
void push(Object o) | 将指定元素添加到集合的开头 |
Object pop() | 移除并返回集合的第一个元素 |
5、案例演示:创建与操作学生链表
- 创建Example2102
package net.hw.lesson21;
import java.util.LinkedList;
/**
* 功能:创建与操作学生链表
* 作者:华卫
* 日期:2020年05月23日
*/
public class Example2102 {
public static void main(String[] args) {
// 创建学生链表(采用泛型更加安全)
LinkedList<String> students = new LinkedList<>();
// 判断学生链表是否为空
System.out.println("students.isEmpty(): " + students.isEmpty());
// 添加三个元素
System.out.print("链表添加三个元素:");
students.add("李晓红");
students.add("吴文燕");
students.add(0, "王晓刚");
// 输出这个链表
System.out.println(students);
// 在链表头添加元素
System.out.print("在链表头添加元素:");
students.push("毛晓玲");
// 输出整个链表
System.out.println(students);
// 在链表尾添加元素
System.out.print("在链表尾添加元素:");
students.offer("郑小翠");
// 输出整个链表
System.out.println(students);
// 获取第一个元素
System.out.println("链表第一个元素:" + students.getFirst()); // 等价于 students.peek()、students.peekFirst()
// 获得最后一个元素
System.out.println("链表最后一个元素:" + students.getLast()); // 等价于 students.peekLast()
// 获取指定位置的元素
int index = 2;
System.out.println("students[" + index + "] = " + students.get(index));
// 遍历学生链表
System.out.print("采用for循环遍历链表:");
for (int i = 0; i < students.size(); i++) {
System.out.print(students.get(i) + " ");
}
System.out.println();
System.out.print("采用foreach循环遍历链表:");
for (String student : students) {
System.out.print(student + " ");
}
System.out.println();
// 删除第一个元素
System.out.print("删除第一个元素:");
students.remove(0); // 等价于 students.removeFirst()、students.poll()、students.pollFirst()
// 输出整个链表
System.out.println(students);
// 删除最后一个元素
System.out.print("删除最后一个元素:");
students.pollLast(); // 等价于 students.remove(students.size() - 1)
// 输出整个链表
System.out.println(students);
// 删除下标为1的元素
System.out.print("删除下标为1的元素:");
students.remove(1);
// 输出整个链表
System.out.println(students);
// 删除全部链表元素
students.clear();
System.out.println("删除全部元素:" + students);
}
}
- 运行程序,查看结果
- 思考题:如何反序输出链表的全部元素?
六、Collection集合遍历
(一)Iterator遍历集合
1、Iterator接口定义
Iterator接口是Java集合框架中的一员,主要用于迭代访问(即遍历)Collection中的元素,因此Iterator对象也被称为迭代器。
2、案例演示:利用迭代器遍历学生数组列表
- 创建Example2103
package net.hw.lesson21;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 功能:利用迭代器遍历学生数组列表
* 作者:华卫
* 日期:2020年05月23日
*/
public class Example2103 {
public static void main(String[] args) {
// 创建学生数组列表
ArrayList<String> students = new ArrayList<>();
// 添加列表元素
students.add("李晓红");
students.add("郑晓彤");
students.add("邱子涵");
students.add("吴晓燕");
students.add("董文刚");
// 由列表获取迭代器
Iterator iterator = students.iterator();
// 利用迭代器遍历数组列表
while (iterator.hasNext()) { // 判断迭代器是否有下一个元素
// 获取迭代器下一个元素
Object object = iterator.next();
System.out.print(object + " ");
}
}
}
- 运行程序,查看结果
3、Iterator遍历集合的工作原理
- Iterator遍历集合时,内部采用指针的方式来跟踪集合中的元素。在调用next()方法之前,索引位于第一个元素之前,不指向任何元素。
- 第一次调用next()方法后,索引会向后移动一位,指向第一个元素并将该元素返回;
- 再次调用next()方法时,索引会指向第二个元素并将该元素返回;
- 以此类推,直到hasNext()方法返回false,表示到达了集合的末尾终止对元素的遍历。
4、脚下留心
Iterator迭代器对集合中的元素进行迭代时,如果调用了集合对象的remove()方法删除元素,会抛出ConcurrentModificationException
(并发修改异常)。
- 创建Example2106
package net.hw.lesson21;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 功能:演示并发修改异常
* 作者:华卫
* 日期:2020年05月23日
*/
public class Example2106 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("李香兰");
names.add("陈慧娴");
names.add("童安格");
names.add("谭咏麟");
System.out.println("姓名列表:" + names);
Iterator<String> iterator = names.iterator();
while (iterator.hasNext()) {
String name = iterator.next();
if (name.equals("童安格")) {
names.remove(name);
}
}
System.out.println("姓名列表:" + names);
}
}
- 运行程序,查看结果
可是并没有抛出ConcurrentModificationException
异常,这是怎么回事呢?
(二)foreach遍历集合
1、foreach循环定义
foreach循环是一种更加简洁的for循环,也称增强for循环,用于遍历数组或集合中的元素。
2、foreach循环语法格式
for (容器中元素类型 临时变量 :容器变量) {
// 执行语句
}
比如:
for (Object obj : list) {
System.out.println(obj);
}
3、案例演示:利用增强for循环遍历学生数组列表
- 创建Example2104
package net.hw.lesson21;
import java.util.ArrayList;
/**
* 功能:利用增强for循环遍历学生数组列表
* 作者:华卫
* 日期:2020年05月23日
*/
public class Example2104 {
public static void main(String[] args) {
// 创建学生数组列表
ArrayList<String> students = new ArrayList<>();
// 添加列表元素
students.add("李晓红");
students.add("郑晓彤");
students.add("邱子涵");
students.add("吴晓燕");
students.add("董文刚");
// 利用增强for循环遍历数组列表
for (String student : students) {
System.out.print(student + " ");
}
}
}
- 运行程序,查看结果
4、脚下留心
foreach循环遍历集合和数组时,只能访问集合中的元素,不能对其中的元素进行修改。
- 创建Example2104
package net.hw.lesson21;
import java.util.ArrayList;
/**
* 功能:增强for循环不能修改集合元素
* 作者:华卫
* 日期:2020年05月23日
*/
public class Example2105 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("李香兰");
names.add("陈慧娴");
names.add("童安格");
names.add("谭咏麟");
System.out.println("姓名列表:" + names);
// 利用增强for循环遍历数组
for (String name : names) {
name = "萌萌哒";
}
System.out.println("增强for循环修改后的列表:" + names);
// 利用普通for循环遍历数组
for (int i = 0; i < names.size(); i++) {
names.set(i, "萌萌哒");
}
System.out.println("普通for循环修改后的列表:" + names);
}
}
- 运行程序,查看结果
- 解释说明:从运行结果可以看出来,增强for循环并不能修改列表中元素的值。其原因是第22行代码中的
name = "萌萌哒";
只是将临时变量name指向了一个新的字符串,这与列表中的元素没有一点关系,而在普通for循环里,可以通过索引来修改列表中指定元素的值。
(三)在JDK 8里用forEach遍历集合
1、针对所有集合都有效的遍历
(1)遍历方法
forEach(Consumer action)方法是JDK 8中新增的遍历集合元素的方法,根据Lambda表达式特性,该方法所需要的参数是一个函数式接口。比如list.forEach(obj -> System.out.println("迭代集合元素:" + obj));
(2)案例演示:遍历姓名数组列表
- 创建Example2107
package net.hw.lesson21;
import java.util.ArrayList;
/**
* 功能:演示forEach遍历集合
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2107 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("李香兰");
names.add("陈慧娴");
names.add("童安格");
names.add("谭咏麟");
System.out.println("姓名列表:" + names);
// 使用JDK8新增的forEach(Consumer action)方法遍历集合
System.out.print("采用forEach方法遍历:");
names.forEach(name -> System.out.print(name + " "));
}
}
- 运行程序,查看结果
- 简要说明:使用列表的forEach(Consumer action)方法遍历集合,该方法传入一个Lambda表达式形式书写的函数式接口,在执行时,会自动遍历集合元素,并将元素逐个传递给Lambda表达式的形参。
2、针对迭代器提供的遍历方法
(1)遍历方法
JDK 8中还针对Iterator迭代器对象提供了一个forEachRemaining(Consumer action)方法来进行遍历,该方法同样需要一个函数式接口。
Iterator it = list.iterator();
it.forEachRemaining(obj -> System.out.println("迭代集合元素:" + obj));
(2)案例演示:遍历姓名数组列表
- 创建Example2108
package net.hw.lesson21;
import java.util.ArrayList;
import java.util.Iterator;
/**
* 功能:演示forEachRemaining遍历迭代器
* 作者:华卫
* 日期:2020年05月24日
*/
public class Example2108 {
public static void main(String[] args) {
ArrayList<String> names = new ArrayList<>();
names.add("李香兰");
names.add("陈慧娴");
names.add("童安格");
names.add("谭咏麟");
System.out.println("姓名列表:" + names);
// 获取数组列表的迭代器
Iterator<String> iterator = names.iterator();
// 使用JDK8新增的forEachRemaining(Consumer action)方法遍历迭代器
System.out.print("采用forEachRemaining方法遍历:");
iterator.forEachRemaining(name -> System.out.print(name + " "));
}
}
- 运行程序,查看结果
3、小结List集合的遍历
遍历List集合有三种方法:
- foreach循环遍历集合
- forEach()方法遍历集合
- forEachRemaining()方法遍历集合
这三种方法,对于单例集合Collection都是有效的,比如下一讲我们要学习的Set集合,也可以用上述三种方法进行遍历。