这节我们学习集合相关内容。
一、集合概述
在Java中,集合(Collection)是一种用于存储和操作一组对象的数据结构。它提供了一组接口和类,用于处理和操作对象的集合。
集合框架(Collection Framework)是Java中用于表示和操作集合的一组类和接口。它位于 java.util 包中,并提供了一系列的接口和类,包括集合接口 (Collection)、列表接口(List)、集合类(Set)、映射接口(Map)等。
集合框架的主要目标是提供一种通用的方式来存储和操作对象的集合,无论集合的具体实现方式如何,用户都可以使用统一的接口和方法来操作集合。
集合理解:
集合和数组都可以存储多个元素值,对比数组,我们来理解下集合:
- 数组的长度是固定的,集合的长度是可变的
- 数组中存储的是同一类型的元素,集合中存储的数据可以是不同类型的
- 数组中可以存放基本类型数据或者引用类型变量,集合中只能存放引用类型变量
- 数组是由JVM中现有的 类型+[ ] 组合而成的,除了一个 length 属性 ,还有从 Object 中继承过来的方法之外,数组对象就调用不到其他属性和方法了
- 集合框架由 java.util 包下多个接口和实现类组成,定义并实现了很多方法,功能强大
二、框架体系
2.1 组成要素
集合框架主要由三个要素组成:
1)接口
整个集合框架的上层结构,都是用接口进行组织的。接口中定义了集合中必须要有的基本方法。 通过接口还把集合划分成了几种不同的类型,每一种集合都有自己对应的接口。
2)实现类
对于上层使用接口划分好的集合种类,每种集合的接口都会有对应的实现类。 每一种接口的实现类很可能有多个,每个的实现方式也会各有不同。
3)数据结构
每个实现类都实现了接口中所定义的最基本的方法,例如对数据的存储、检索、操作等方法。但是不同的实现类,它们存储数据的方式不同,也就是使用的数据结构不同。
集合框架继承体系图:
2.2 集合分类
1)单列集合(Single Column Collection)
根接口: java.util.Collection 单列集合是指每个集合元素只包含一个单独的对象,它是集合框架中最简单的形式
2)多列集合(Multiple Column Collection)
根接口: java.util.Map 多列集合是指每个集合元素由多个列(字段)组成,可以同时存储和操作多个相关的值
Collection 接口结构图:
Map 接口结构图:
三、Collection
Collection接口是单列集合类的父接口,这种集合可以将数据一个一个的存放到集合中。它有两个重要的子接口,分别是 java.util.List 和 java.util.Set
如图:
Collection是父接口,其中定义了单列集合(List和Set)通用的一些方法, Collection接口的实现类,都可以使用这些方法。
1)Collection集合基础方法
public interface Collection<E> extends Iterable<E> {
//省略...
//向集合中添加元素
boolean add(E e)
//清空集合中所有的元素
void clear()
//判断当前集合中是否包含给定的对象
boolean contains(Object o)
//判断当前集合是否为空。
boolean isEmpty()
//把给定的对象,在当前集合中删除
boolean remove(Object o)
//返回集合中元素的个数
int size()
//把集合中的元素,存储到数组中
Object[] toArray()
}
2)Collection 泛型约束
通过泛型约束避免 “运行时类型转换异常”:
- 没有泛型时,集合可以存储任意类型元素,取出时需要强制转换,容易出现 ClassCastException;
- 有泛型后,集合在声明时指定<存储的数据类型>,编译器会自动检查元素类型,不符合则编译报错
正确写法:集合接口引用指向实现类对象,固定书写格式:
接口类型 接口引用名 = new 实现类<>(构造方法实参);
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
public class GenericDemo {
public static void main(String[] args) {
// 1. 接口引用指向实现类(最推荐,符合面向接口编程)
Collection<String> coll = new ArrayList<>(); // 只能且只能存String
List<Integer> list = new LinkedList<>(); // 有且只能存Integer
// 2. 具体实现类引用指向实现类(合法,但灵活性低)
ArrayList<Student> studentList = new ArrayList<>(); // 只能存Student
// 存入正确类型元素(编译通过)
coll.add("Java");
list.add(100);
studentList.add(new Student("张三", 20));
}
}
class Student {
private String name;
private int age;
public Student(String name, int age) {
this.name = name;
this.age = age;
}
}
3)Collection 泛型补充
泛型支持 “父类型泛型引用指向子类型元素”,但反之不允许:
// 正确:集合中可以存Student的子类(假设Graduate是Student的子类)
ArrayList<Student> students = new ArrayList<>();
students.add(new Graduate("王五", 22)); // 合法,Graduate是Student的子类
// 错误:父类型元素不能存入子类型泛型集合
ArrayList<Graduate> graduates = new ArrayList<>();
graduates.add(new Student("赵六", 21)); // 编译报错,Student不是Graduate的子类
泛型参数方法:
public interface Collection<E> extends Iterable<E> {
//省略...
//把一个指定集合中的所有数据,添加到当前集合中
boolean addAll(Collection<? extends E> c)
//判断当前集合中是否包含给定的集合的所有元素。
boolean containsAll(Collection<?> c)
//把给定的集合中的所有元素,在当前集合中删除。
boolean removeAll(Collection<?> c)
//判断俩个集合中是否有相同的元素,如果有当前集合只保留相同元素,如果没有当前集合元素清空
boolean retainAll(Collection<?> c)
//把集合中的元素,存储到数组中,并指定数组的类型
<T> T[] toArray(T[] a)
//返回遍历这个集合的迭代器对象 Iterator<E> iterator()
}
泛型方法测试案例:
import java.util.ArrayList;
import java.util.Collection;
// 泛型方法测试
public class Test_Element {
public static void main(String[] args) {
// 1. 实例化两个集合对象,专门存放 String 类型元素
// 集合实例化对象 固定写法
Collection<String> c1 = new ArrayList<>();
Collection<String> c2 = new ArrayList<>();
// 2. 分别往 c1 和 c2 集合中添加元素
String s1 = "hello";
String s2 = "world";
c1.add(s1);
c1.add(s2);
String s3 = "nihao";
String s4 = "hello";
String s5 = "okok";
c2.add(s3);
c2.add(s4);
c2.add(s5);
System.out.println("c1: " + c1);
System.out.println("c2: " + c2);
System.out.println("-----------");
// 3. 将 c2 集合整体添加到 c1 中
c1.addAll(c2);
System.out.println("c1.size: " + c1.size());
System.out.println("after addAll(c2), c1: " + c1);
System.out.println("-----------");
// 4. 判断是否包含指定元素
boolean f = c1.contains("hello");
System.out.println("contains hello: " + f);
// 5. 创建 s6 对象,判断集合中是否包含该对象
// 注意: s6 的地址 和 "world" 地址不一样
// s6 是堆中临时 new 出来的,"world" 存在堆中的字符串常量池中
String s6 = new String("world");
// 结果显示 true,说明集合 contains 方法借助 equals 方法进行比较,而非 ==
f = c1.contains(s6);
System.out.println("contains(s6): " + f);
System.out.println("-----------");
// 6. 判断是否包含 c2 对象
f = c1.containsAll(c2);
System.out.println("containsAll(c2): " + f);
System.out.println("-----------");
// 7. 删除指定元素【底层借助 equals 比较,然后删除】
f = c1.remove(s6);
System.out.println("remove(s6): " + f);
System.out.println("after remove, c1: " + c1);
System.out.println("-----------");
// 8. 删除 c2 整个集合【底层实现:遍历 c2,逐个元素 equals 比较,然后删除】
f = c1.removeAll(c2);
System.out.println("removeAll(c2): " + f);
System.out.println("after remove, c1: " + c1);
}
}
4)集合存放自定义类对象
定义Student类,创建多个对象放入集合中。测试集合的contains和remove方法。
注意:先不重写equals方法进行测试,再重写equals方法进行测试。
Student 学生类:
public class Student {
private String name;
private int age;
public Student() {
}
public Student(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 "name:"+name+",age:"+age;
}
/*
@Override
public boolean equals(Object obj){
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Student other = (Student)obj;
if (name == null) {
if (other.name != null) {
return false;
}
}else if (!name.equals(other.name)) {
return false;
}
if (age != other.age) {
return false;
}
return true;
} */
}
测试类:
import java.util.ArrayList;
import java.util.Collection;
public class Test_Student {
public static void main(String[] args) {
//准备学生对象 让其中两个学生对象的属性一模一样
Student s1 = new Student("zs",20);
Student s2 = new Student("ls",19);
Student s3 = new Student("ww",22);
Student s4 = new Student("tom",18);
Student s5 = new Student("zs",20);
//1.定义只能存储Student对象的集合
Collection<Student> coll = new ArrayList<>();
//2.往集合里添加元素
coll.add(s1);
coll.add(s2);
coll.add(s3);
coll.add(s4);
coll.add(s5);
//3.输出集合元素个数,输出集合对象
System.out.println("coll.size:"+coll.size());
System.out.println("coll:"+coll);
System.out.println("---------------------");
//4.判断s5是否存在
boolean flag = coll.contains(s5);
System.out.println("contains(s5):"+flag);
//5.删除s5
flag = coll.remove(s5);
System.out.println("remove(s5):"+flag);
System.out.println("coll.size:"+coll.size());
System.out.println("coll:"+coll);
}
}
注意事项:集合中contains、remove等方法,底层借助元素对象的equals方法进行值比较,所以如果要用集合存放自定义类对象,注意重写自定义类的equals方法!
四、集合遍历
单列集合的遍历,一般有3种方法:
4.1 toArray
借助Collection接口中toArray()方法实现,方法原型为:Object[ ] toArray();
遍历格式:
//将集合转化成数组
Object[] array = 集合引用.toArray();
//遍历数组
for (int i = 0; i < array.length; i++) {
System.out.println(array[i]);
}
4.2 迭代器
迭代器是集合框架提供的一种遍历集合元素的方式。通过调用集合的 iterator() 方法可以获取一个迭代器对象,然后使用迭代器的 hasNext() 方法判断是否还有下一个元素,使用 next() 方法获取下一个元素。
固定格式:
//1.获取迭代器对象
Iterator<集合元素类型> iterator = 集合对象.iterator();
//2.借助迭代器中hasNext()和next()方法完成遍历
while (iterator.hasNext()) {
//获取集合元素
集合元素类型 变量名 = iterator.next();
//对集合元素进行输出
System.out.println(变量名);
}
示例:
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
public class CollectionTraverse {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("Python");
coll.add("C++");
// 1. 获取迭代器对象
Iterator<String> it = coll.iterator();
// 2. 循环遍历(hasNext()判断是否有下一个元素)
while (it.hasNext()) {
// 3. next()获取下一个元素
String element = it.next();
System.out.println(element);
// 遍历中删除元素(只能用迭代器的remove()方法)
if (element.equals("Python")) {
it.remove(); // 安全删除当前元素
}
}
System.out.println("删除后集合:" + coll); // [Java, C++]
}
}
- 迭代器的优点是提供了一种统一的遍历方式,对单列集合(直接或间接实现了Collection接口),都可以通过迭代器按顺序访问元素
- 迭代器隐藏了集合的具体实现细节,提供了一种抽象的访问接口,使代码更加灵活和可复用
- 如果在迭代过程中对集合进行修改(添加、删除元素),可能会导致迭代器抛出 ConcurrentModificationException 异常。因此,在使用迭代器遍历时,不要修改集合结构
4.3 foreach
除了使用迭代器遍历集合之外,JDK1.5及以后版本JDK,提供了增强for循环实现集合遍历,这种方式相对迭代器遍历更简单。
遍历格式:
for(集合元素类型 变量名 : 集合) {
//操作元素变量
}
每次循环,引用变量会指向集合中的一个元素对象,然后在循环体中对该元素对象进行操作
示例:
import java.util.ArrayList;
import java.util.Collection;
public class CollectionTraverse {
public static void main(String[] args) {
Collection<String> coll = new ArrayList<>();
coll.add("Java");
coll.add("Python");
coll.add("C++");
// 增强for循环遍历
for (String element : coll) {
System.out.println(element); // 直接获取每个元素
}
}
}
五、List 接口
java.util.List 接口继承了 Collection 接口,是常用的一种集合类型。
List 集合具有 Collection 集合的特点之外,还具有自己的一些特点:
- List 是一种有序集合
例如,向集合中存储的元素顺序是8、2、5。那么集合中就是按照这个顺序进行存储的
- List 是一种带索引的集合
可以通过元素的下标索引,精确查找对应的元素数据
- List集合可以存放重复元素
可以把相同的数据,在List集合中多次保存
5.1 继承体系
List接口继承了Collection接口,Collection接口继承了Iterable接口。
List接口的实现类:
5.2 常用方法
//返回集合中指定位置的元素。
E get(int index);
//用指定元素替换集合中指定位置的元素,并返回被替代的旧元素。
E set(int index, E element);
//将指定的元素,添加到该集合中的指定位置上。
void add(int index, E element);
//从指定位置开始,把另一个集合的所有元素添加进来
boolean addAll(int index, Collection<? extends E> c);
//移除列表中指定位置的元素, 并返回被移除的元素。
E remove(int index);
//查收指定元素在集合中的所有,从前往后查到的第一个元素(List集合可以重复存放数据)
int indexOf(Object o);
//查收指定元素在集合中的所有,从后往前查到的第一个元素(List集合可以重复存放数据)
int lastIndexOf(Object o);
//根据指定开始和结束位置,截取出集合中的一部分数据
List<E> subList(int fromIndex, int toIndex);
注意:除了这些方法之外,还有从父接口Collection中继承过来的方法
示例:
import java.util.ArrayList;
import java.util.List;
public class ListInheritMethodDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
// 1. add(E e):末尾添加
list.add(10);
list.add(20);
list.add(30);
System.out.println("集合:" + list); // [10, 20, 30]
// 2. addAll(Collection):添加另一个集合
List<Integer> other = new ArrayList<>();
other.add(40);
other.add(50);
list.addAll(other);
System.out.println("添加other后:" + list); // [10, 20, 30, 40, 50]
// 3. contains(Object o):判断包含
boolean has20 = list.contains(20);
System.out.println("是否包含20:" + has20); // true
// 4. remove(Object o):删除首次出现的元素
boolean removed = list.remove(Integer.valueOf(30)); // 注意:int需装箱为Integer
System.out.println("是否删除30:" + removed); // true
System.out.println("删除后:" + list); // [10, 20, 40, 50]
// 5. size():元素个数
System.out.println("元素个数:" + list.size()); // 4
// 6. clear():清空
list.clear();
System.out.println("清空后是否为空:" + list.isEmpty()); // true
}
}
5.3 ArrayList
java.util.ArrayList 是最常用的一种List类型集合, ArrayList 类底层使用动态数组来实现数据的存储,所以它的特点是:增删慢,查找快。
在日常的开发中,查询数据也是用的最多的功能,所以ArrayList是最常用的集合。
但是,如果项目中对性能要求较高,并且在集合中大量的数据做增删操作,那 ArrayList 就不太适合了。
示例:
import java.util.ArrayList;
public class ArrayListDemo {
public static void main(String[] args) {
// 1. 创建ArrayList(指定初始容量为5,避免频繁扩容)
ArrayList<String> list = new ArrayList<>(5);
// 2. 添加元素
list.add("Java");
list.add("Python");
list.add("C++");
list.add(1, "Go"); // 在索引1处插入"Go"
System.out.println("添加后:" + list); // [Java, Go, Python, C++]
// 3. 获取元素
String element = list.get(2);
System.out.println("索引2的元素:" + element); // Python
// 4. 修改元素
String old = list.set(3, "JavaScript");
System.out.println("替换的旧元素:" + old); // C++
System.out.println("修改后:" + list); // [Java, Go, Python, JavaScript]
// 5. 删除元素
String removed = list.remove(1); // 删除索引1的元素
System.out.println("删除的元素:" + removed); // Go
System.out.println("删除后:" + list); // [Java, Python, JavaScript]
// 6. 查询操作
int index = list.indexOf("Python");
System.out.println("Python的索引:" + index); // 1
boolean contains = list.contains("Java");
System.out.println("是否包含Java:" + contains); // true
// 7. 其他操作
System.out.println("元素个数:" + list.size()); // 3
list.clear();
System.out.println("清空后是否为空:" + list.isEmpty()); // true
}
}
5.4 LinkedList
java.util.LinkedList 底层采用的数据结构是双向链表,其特点是:增删快,查找慢
它的特点刚好和 ArrayList 相反,所以在代码中,需要对集合中的元素做大量的增删操作的时候,可以选择使用 LinkedList 。
注意:这里描述的快和慢,需要在大量的数据操作下,才可以体现,如果数据量不大的话,每一种集合的操作几乎没有任何区别。
示例:
import java.util.LinkedList;
public class LinkedListDemo {
public static void main(String[] args) {
// 1. 创建LinkedList(无需指定初始容量,链表无“容量”概念)
LinkedList<String> list = new LinkedList<>();
// 2. List基础操作:添加、获取、修改
list.add("A"); // 尾部添加(等同于addLast)
list.add(1, "B"); // 索引1处插入
list.add("C");
System.out.println("初始列表:" + list); // [A, B, C]
String elem = list.get(2); // 获取索引2的元素
System.out.println("索引2的元素:" + elem); // C
String oldElem = list.set(1, "B+"); // 修改索引1的元素
System.out.println("修改的旧元素:" + oldElem); // B
System.out.println("修改后列表:" + list); // [A, B+, C]
// 3. Deque双端队列操作:头部/尾部增删查
list.addFirst("Head"); // 头部添加
list.addLast("Tail"); // 尾部添加
System.out.println("双端操作后:" + list); // [Head, A, B+, C, Tail]
String first = list.getFirst(); // 获取头部
String last = list.getLast(); // 获取尾部
System.out.println("头部元素:" + first + ",尾部元素:" + last); // Head、Tail
list.removeFirst(); // 删除头部
list.removeLast(); // 删除尾部
System.out.println("删除头尾后:" + list); // [A, B+, C]
// 4. 栈操作(LIFO)
list.push("Top1"); // 栈顶添加(头部)
list.push("Top2");
System.out.println("栈结构:" + list); // [Top2, Top1, A, B+, C]
String popElem = list.pop(); // 栈顶删除(头部)
System.out.println("弹出的栈顶元素:" + popElem); // Top2
System.out.println("弹出后栈:" + list); // [Top1, A, B+, C]
// 5. 其他操作
System.out.println("是否包含A:" + list.contains("A")); // true
System.out.println("元素个数:" + list.size()); // 4
list.clear();
System.out.println("清空后是否为空:" + list.isEmpty()); // true
}
}
对比维度 | ArrayList |
LinkedList |
---|---|---|
底层结构 | 动态数组(连续存储) | 双向链表(非连续存储) |
随机访问(get ) |
效率高(O(1) ) |
效率低(O(n) ) |
头部 / 尾部增删 | 效率低(O(n) ,需移动元素) |
效率高(O(1) ,仅改指针) |
中间位置增删 | 效率低(O(n) ) |
效率中等(O(n) 定位 +O(1) 修改) |
内存占用 | 可能有冗余空间(扩容预留) | 每个节点额外存储前后指针(内存开销更大) |
适用场景 | 读多写少、频繁随机访问 | 写多读少、频繁头部 / 尾部操作 |
5.5 Vector
Vector是在JDK1.0引入的,它实现了List接口,属于Java集合框架的一部分,其基于动态数组(Dynamic Array)实现,线程安全,Vector在功能和使用方式上和 ArrayList非常相似。
ArrayList是在JDK 1.2引入的,非线程安全,但单线程环境下性能更高效,是 Vector的一个非线程安全的替代品。
示例:
import java.util.Vector;
public class VectorDemo {
public static void main(String[] args) {
// 1. 创建Vector(指定初始容量10,扩容增量5)
Vector<String> vector = new Vector<>(10, 5);
// 2. 添加元素
vector.add("Java");
vector.add("Python");
vector.add(1, "C++"); // 索引1处插入
System.out.println("添加后:" + vector); // [Java, C++, Python]
// 3. 获取与修改元素
String elem = vector.get(2);
System.out.println("索引2的元素:" + elem); // Python
String oldElem = vector.set(0, "JavaScript");
System.out.println("修改的旧元素:" + oldElem); // Java
System.out.println("修改后:" + vector); // [JavaScript, C++, Python]
// 4. 删除元素
vector.remove(1); // 删除索引1的元素
System.out.println("删除后:" + vector); // [JavaScript, Python]
// 5. 其他操作
System.out.println("是否包含Python:" + vector.contains("Python")); // true
System.out.println("元素个数:" + vector.size()); // 2
// 6. 枚举器遍历(特有方式,类似迭代器)
System.out.println("枚举器遍历:");
var enumeration = vector.elements();
while (enumeration.hasMoreElements()) {
System.out.println(enumeration.nextElement());
}
}
}
对比维度 | Vector |
ArrayList |
---|---|---|
线程安全 | 线程安全(方法被 synchronized 修饰) |
线程不安全(无同步机制) |
性能 | 单线程环境下性能较低(同步开销) | 单线程环境下性能更高 |
扩容机制 | 默认扩容至原来的 2 倍,可指定增量 | 默认扩容至原来的 1.5 倍,不可指定增量 |
迭代器 | 支持 Enumeration 和 Iterator |
仅支持 Iterator 和 ListIterator |
初始容量 | 默认初始容量 10 | 无参构造器初始容量为 0(JDK 8+) |
六、List 小结
这节学习了 Collection 与 List 相关内容,下节我们再进一步学习 Set 等相关内容。