Java集合框架详解
Java 集合框架详解
本章位置:Java 核心基础
学习目标:理解 Java 集合框架的整体结构,掌握Collection、List、Set、Map、Iterator、泛型、排序、Collections工具类等核心内容,并能在实际开发中正确选择集合类型。
一、为什么需要集合框架
前面学习数组时,我们已经知道:
int[] numbers = new int[5];
数组能够保存多个同类型数据。
但是数组有一个明显限制:
长度固定
例如:
int[] numbers = new int[5];
创建之后:
长度永远是 5
如果后面需要:
添加第 6 个数据
删除某一个数据
动态扩容
方便查找
去重
按照键和值保存
数组就会比较麻烦。
Java 因此提供:
集合框架
英文:
Java Collections Framework
简称:
JCF
二、集合和数组的区别
| 对比 | 数组 | 集合 |
|---|---|---|
| 长度 | 固定 | 通常可动态变化 |
| 数据类型 | 可保存基本类型和引用类型 | 主要保存对象 |
| 添加删除 | 比较麻烦 | API 丰富 |
| 查找 | 需要自己处理 | 很多集合提供现成功能 |
| 去重 | 需要自己实现 | Set 天然支持 |
| 键值结构 | 不支持 | Map 支持 |
| 排序 | 借助 Arrays |
可使用 Collections、比较器等 |
三、集合框架整体结构
Java 集合可以先分成两大体系:
Collection
Map
大致结构:
Java 集合框架
│
├─ Collection
│ │
│ ├─ List
│ │ ├─ ArrayList
│ │ ├─ LinkedList
│ │ └─ Vector
│ │
│ ├─ Set
│ │ ├─ HashSet
│ │ ├─ LinkedHashSet
│ │ └─ TreeSet
│ │
│ └─ Queue / Deque
│ ├─ LinkedList
│ ├─ ArrayDeque
│ └─ PriorityQueue
│
└─ Map
│
├─ HashMap
├─ LinkedHashMap
├─ TreeMap
└─ Hashtable
学习时最重要的是:
List
Set
Map
四、Collection 是什么
Collection 是很多单列集合的顶层接口。
例如:
List
Set
Queue
都属于:
Collection
可以理解:
Collection
↓
用于保存一组对象
五、Collection 常用方法
常见方法:
add()
remove()
contains()
size()
isEmpty()
clear()
addAll()
removeAll()
retainAll()
containsAll()
toArray()
六、Collection 基础示例
import java.util.ArrayList;
import java.util.Collection;
public class Demo {
public static void main(String[] args) {
Collection<String> collection =
new ArrayList<>();
collection.add("Java");
collection.add("MySQL");
collection.add("Spring");
System.out.println(collection);
}
}
输出类似:
[Java, MySQL, Spring]
七、size()
获取元素数量:
System.out.println(
collection.size()
);
如果有 3 个元素:
3
注意:
数组:array.length
字符串:str.length()
集合:collection.size()
八、isEmpty()
判断集合是否为空:
if (collection.isEmpty()) {
System.out.println(
"集合为空"
);
}
九、add()
添加元素:
collection.add("Redis");
十、remove()
删除指定元素:
collection.remove("MySQL");
十一、contains()
判断是否包含:
boolean result =
collection.contains("Java");
十二、clear()
清空:
collection.clear();
清空之后:
collection.size()
变成:
0
十三、集合只能直接保存对象
错误:
ArrayList<int>
Java 泛型不能直接使用基本类型。
正确:
ArrayList<Integer>
因为:
int
对应包装类
Integer
十四、自动装箱让使用更简单
例如:
List<Integer> list =
new ArrayList<>();
list.add(10);
list.add(20);
虽然集合保存的是:
Integer
但 Java 自动完成:
int → Integer
这就是:
自动装箱
十五、泛型
集合中经常看到:
List<String>
List<Integer>
尖括号里的:
String
Integer
就是:
泛型参数
十六、为什么需要泛型
如果没有泛型:
List list =
new ArrayList();
list.add("Java");
list.add(100);
list.add(true);
什么都可以放进去。
取出来:
Object value =
list.get(0);
还需要强制类型转换。
很容易出现:
ClassCastException
十七、使用泛型
List<String> list =
new ArrayList<>();
表示:
这个集合主要保存 String
错误:
list.add(100);
编译器直接阻止。
所以泛型的重要价值:
类型安全
减少强制转换
代码更清晰
十八、泛型的钻石语法
完整:
List<String> list =
new ArrayList<String>();
Java 7 以后通常写:
List<String> list =
new ArrayList<>();
右边:
<>
叫:
diamond operator
钻石操作符
十九、List
List 是:
有序
可重复
有索引
的集合。
例如:
List<String> list =
new ArrayList<>();
list.add("Java");
list.add("MySQL");
list.add("Java");
允许重复:
Java
MySQL
Java
二十、List 的核心特点
1. 元素有顺序
2. 可以重复
3. 可以通过索引访问
4. 索引从 0 开始
和数组很像。
二十一、List 常用方法
除了 Collection 方法,还经常使用:
get(index)
set(index, value)
add(index, value)
remove(index)
indexOf(value)
lastIndexOf(value)
subList(...)
二十二、get()
List<String> list =
new ArrayList<>();
list.add("Java");
list.add("MySQL");
list.add("Redis");
System.out.println(
list.get(0)
);
输出:
Java
二十三、set()
修改指定索引:
list.set(
1,
"Spring"
);
原来:
MySQL
变成:
Spring
二十四、指定位置 add()
list.add(
1,
"Redis"
);
原来:
Java
Spring
会变成:
Java
Redis
Spring
后面的元素向后移动。
二十五、remove() 的重载问题
对于:
List<Integer>
要特别注意:
remove(1)
可能表示:
删除索引 1
而不是:
删除值 1
例如:
List<Integer> list =
new ArrayList<>();
list.add(10);
list.add(20);
list.add(30);
list.remove(1);
删除的是:
20
二十六、删除 Integer 指定值
如果想删除:
值 20
可以:
list.remove(
Integer.valueOf(20)
);
二十七、indexOf()
int index =
list.indexOf("Java");
找不到:
-1
二十八、List 遍历方式
常见:
普通 for
增强 for
Iterator
ListIterator
forEach
二十九、普通 for 遍历
for (int i = 0;
i < list.size();
i++) {
System.out.println(
list.get(i)
);
}
适合:
需要索引
三十、增强 for
for (String item : list) {
System.out.println(item);
}
适合:
只读取元素
三十一、Iterator 迭代器
Iterator 是 Java 集合非常重要的遍历方式。
例如:
Iterator<String> iterator =
list.iterator();
三十二、Iterator 基本方法
hasNext()
next()
remove()
三十三、Iterator 遍历
Iterator<String> iterator =
list.iterator();
while (iterator.hasNext()) {
String value =
iterator.next();
System.out.println(value);
}
三十四、hasNext()
判断:
后面还有没有元素
三十五、next()
获取:
下一个元素
并移动迭代器位置。
三十六、迭代器删除
遍历过程中需要删除当前元素时:
Iterator<String> iterator =
list.iterator();
while (iterator.hasNext()) {
String value =
iterator.next();
if ("Java".equals(value)) {
iterator.remove();
}
}
这是标准做法之一。
三十七、ConcurrentModificationException
看下面代码:
for (String value : list) {
if ("Java".equals(value)) {
list.remove(value);
}
}
在某些情况下会出现:
ConcurrentModificationException
原因:
正在使用迭代器逻辑遍历
同时直接修改集合结构
三十八、为什么增强 for 也会出现并发修改异常
增强 for 遍历集合时:
底层通常就是 Iterator
所以遍历过程中直接:
list.remove(...)
可能破坏迭代器预期结构。
三十九、安全删除方式
方式一:
Iterator.remove()
方式二:
removeIf()
例如:
list.removeIf(
value ->
"Java".equals(value)
);
Lambda 后面会继续学习。
四十、ListIterator
ListIterator 是 List 专用迭代器。
它比 Iterator 功能更多。
例如:
ListIterator<String> iterator =
list.listIterator();
可以:
向前遍历
向后遍历
修改元素
添加元素
四十一、ArrayList
ArrayList 是 Java 最常用集合之一。
基本:
List<String> list =
new ArrayList<>();
特点:
底层主要使用动态数组
四十二、ArrayList 的核心特点
有序
可重复
支持索引
查询快
中间插入删除可能较慢
四十三、ArrayList 为什么查询快
例如:
list.get(100);
ArrayList 底层是:
数组结构
可以根据索引快速定位。
平均意义下:
随机访问非常快
四十四、ArrayList 添加元素
数组长度固定,
但是 ArrayList 可以不断:
add()
原因:
ArrayList 内部会在需要时创建更大的数组,并复制原数据。
所以:
ArrayList 不是数组长度真的变了
而是内部帮我们自动完成扩容
四十五、ArrayList 扩容简单理解
可以简单理解:
原数组空间不够
↓
创建更大的新数组
↓
复制旧数据
↓
继续添加
具体扩容策略属于源码细节,
后面学习源码时再深入。
四十六、ArrayList 适合什么场景
通常适合:
读取多
随机访问多
主要在尾部添加
中间插入删除不频繁
实际业务中:
List 默认优先考虑 ArrayList
往往是一个比较合理的起点。
四十七、LinkedList
LinkedList 是:
双向链表
实现。
例如:
List<String> list =
new LinkedList<>();
四十八、链表简单理解
数组:
[A][B][C][D]
元素连续排列。
链表更像:
A ⇄ B ⇄ C ⇄ D
每个节点会记录:
当前数据
前一个节点
后一个节点
四十九、LinkedList 特点
有序
可重复
支持 List API
也是 Deque 实现
相对于 ArrayList:
随机索引访问较慢
首尾插入删除方便
五十、ArrayList 和 LinkedList 对比
| 操作 | ArrayList | LinkedList |
|---|---|---|
| 底层 | 动态数组 | 双向链表 |
get(index) |
快 | 较慢 |
| 尾部添加 | 通常快 | 快 |
| 首部添加 | 需要移动元素 | 较方便 |
| 中间插入删除 | 需移动数据 | 找到节点后修改链接 |
| 内存 | 相对紧凑 | 节点额外保存引用 |
实际开发不要只背:
“增删多用 LinkedList”
因为:
找到删除位置本身也需要成本
现代 CPU 对连续数组访问很友好
多数普通业务场景:
ArrayList 更常见
五十一、Vector
Vector 是较老的 List 实现。
特点:
很多方法带同步
现代普通代码里:
使用频率明显低于 ArrayList
了解即可。
五十二、Stack
传统:
Stack
用于栈结构。
但现代 Java 通常更推荐使用:
Deque
ArrayDeque
实现栈。
五十三、Set
Set 最大特点:
元素不能重复
例如:
Set<String> set =
new HashSet<>();
set.add("Java");
set.add("MySQL");
set.add("Java");
最后通常只有:
Java
MySQL
五十四、Set 常见实现
HashSet
LinkedHashSet
TreeSet
五十五、HashSet
最常用 Set 实现。
例如:
Set<String> set =
new HashSet<>();
特点:
不能重复
通常不保证插入顺序
查询效率较高
五十六、HashSet 去重基础
Set<Integer> set =
new HashSet<>();
set.add(10);
set.add(20);
set.add(10);
System.out.println(set);
重复的:
10
只保留一个。
五十七、HashSet 如何判断重复
对于对象:
HashSet
主要依赖:
hashCode()
equals()
判断是否重复。
这和前面面向对象章节学习的:
equals / hashCode
直接联系起来。
五十八、自定义对象放入 HashSet
例如:
public class Student {
private String studentNo;
private String name;
}
如果希望:
学号相同
就认为是同一个学生
就应该合理重写:
equals()
hashCode()
五十九、HashSet 去重流程简单理解
可以先理解:
添加元素
↓
计算 hashCode
↓
确定大概位置
↓
如果位置可能冲突
再通过 equals 比较
↓
决定是否重复
这不是完整源码细节,但足够理解基本原理。
六十、为什么重写 equals 还要重写 hashCode
规则:
如果两个对象 equals() 为 true,它们的 hashCode() 必须相同。
否则放入:
HashSet
HashMap
时可能出现不符合预期的结果。
因此 IDEA 中通常:
equals 和 hashCode 一起生成
六十一、LinkedHashSet
LinkedHashSet:
在 HashSet 基础上
额外维护元素迭代顺序
通常可以保留:
插入顺序
例如:
Set<String> set =
new LinkedHashSet<>();
适合:
既要去重
又希望保留原始添加顺序
六十二、TreeSet
TreeSet 特点:
不能重复
可以排序
例如:
Set<Integer> set =
new TreeSet<>();
set.add(30);
set.add(10);
set.add(20);
输出通常:
[10, 20, 30]
六十三、TreeSet 排序需要规则
对于 Integer:
Java 已经知道怎么比较
对于自定义对象:
必须告诉 TreeSet 如何比较
常见方式:
Comparable
Comparator
六十四、Comparable
让类自己具备:
自然排序能力
接口:
Comparable<T>
例如:
public class Student
implements Comparable<Student> {
private int age;
@Override
public int compareTo(
Student other
) {
return Integer.compare(
this.age,
other.age
);
}
}
六十五、compareTo() 返回规则
可以简单记:
负数
当前对象排前面
0
认为排序上相等
正数
当前对象排后面
六十六、不要直接用减法写 compareTo
不推荐:
return this.age
- other.age;
虽然很多简单例子能工作,
但大整数情况下存在:
溢出风险
推荐:
Integer.compare(
this.age,
other.age
);
六十七、Comparator
如果不想修改原类,
或者需要:
多种排序规则
可以使用:
Comparator
例如:
Comparator<Student> comparator =
(a, b) ->
Integer.compare(
a.getAge(),
b.getAge()
);
然后:
new TreeSet<>(
comparator
);
六十八、Comparable 和 Comparator 区别
Comparable
类自己定义默认排序规则
Comparator
外部定义临时或额外排序规则
可以简单记:
Comparable
“我自己会比较”
Comparator
“别人告诉我怎么比较”
六十九、Map
Map 用于保存:
键值对
英文:
key-value
例如:
用户名 → 用户对象
学生学号 → 学生对象
商品 id → 商品对象
配置名称 → 配置值
七十、Map 基础结构
例如:
Map<String, Integer> scores =
new HashMap<>();
表示:
key:String
value:Integer
七十一、Map 和 Collection 的关系
重点:
Map 不是 Collection 的子接口
它们属于:
两套不同体系
所以不要说:
Map 是 Collection
七十二、Map 常用方法
常见:
put()
get()
remove()
containsKey()
containsValue()
size()
isEmpty()
clear()
keySet()
values()
entrySet()
getOrDefault()
putIfAbsent()
七十三、put()
Map<String, Integer> map =
new HashMap<>();
map.put(
"Java",
90
);
map.put(
"MySQL",
85
);
七十四、key 不能重复
如果:
map.put(
"Java",
90
);
map.put(
"Java",
100
);
后面的值会覆盖前面的值。
最后:
Java → 100
七十五、value 可以重复
例如:
map.put("Java", 90);
map.put("Spring", 90);
这是允许的。
所以 Map:
key 不能重复
value 可以重复
七十六、get()
根据 key 获取 value:
Integer score =
map.get("Java");
七十七、get() 找不到
如果 key 不存在:
map.get("Redis");
通常返回:
null
所以:
Integer
自动拆箱成 int 时要小心空指针。
例如:
int score =
map.get("Redis");
可能:
NullPointerException
七十八、getOrDefault()
可以提供默认值:
int score =
map.getOrDefault(
"Redis",
0
);
不存在时得到:
0
七十九、containsKey()
if (
map.containsKey(
"Java"
)
) {
}
八十、containsValue()
boolean result =
map.containsValue(
90
);
八十一、remove()
map.remove("Java");
八十二、Map 遍历 key
for (
String key :
map.keySet()
) {
System.out.println(
key
);
}
八十三、Map 遍历 value
for (
Integer value :
map.values()
) {
System.out.println(
value
);
}
八十四、Map 遍历 entry
最常用:
for (
Map.Entry<String, Integer> entry
: map.entrySet()
) {
System.out.println(
entry.getKey()
+ " = "
+ entry.getValue()
);
}
八十五、为什么 entrySet() 常用
如果同时需要:
key
value
使用:
entrySet()
更直观。
不要每次:
for (String key : map.keySet()) {
Integer value =
map.get(key);
}
如果本来就需要键值对,
entrySet() 通常更合适。
八十六、Map.forEach()
Java 8 以后:
map.forEach(
(key, value) -> {
System.out.println(
key + "=" + value
);
}
);
Lambda 后面会继续学。
八十七、HashMap
HashMap 是 Java 最常用的 Map 实现。
特点:
key 不重复
value 可重复
查询效率高
通常不保证插入顺序
允许 null key 和 null value
实际业务:
Map 默认优先考虑 HashMap
往往很常见。
八十八、HashMap 的 key 为什么要重写 equals 和 hashCode
HashMap 查找 key 时:
会利用 hashCode 和 equals
如果自定义对象作为 key,
没有正确重写:
equals()
hashCode()
可能出现:
明明业务上相同
却查找不到
八十九、自定义对象作为 HashMap key
例如:
Map<Student, String> map =
new HashMap<>();
如果业务上:
studentNo 相同
就是同一个学生
需要让 Student 的:
equals()
hashCode()
基于学号设计。
九十、HashMap 底层结构简单了解
现代 Java HashMap 可以简单理解为:
数组
+
链表
+
红黑树
大致思路:
根据 hash 找桶位置
冲突少时使用链式结构
冲突严重到一定条件时
可能树化
这里先理解思想,
不要一开始死背所有阈值和源码常量。
九十一、什么是哈希冲突
不同 key:
可能计算到相同或相近桶位置
这种情况叫:
哈希冲突
Java 会使用内部结构继续区分。
所以:
hashCode 相同
不代表 equals 一定相同
九十二、HashMap key 的设计建议
如果对象作为 key:
参与 equals/hashCode 的字段
最好在作为 key 期间保持稳定
不要把对象放入 HashMap 后,
又修改关键字段导致:
hashCode 变化
否则可能:
再也找不到这个 key
九十三、LinkedHashMap
LinkedHashMap:
在 HashMap 基础上
额外维护迭代顺序
常见:
保留插入顺序
也可以用于某些:
访问顺序
LRU 缓存
场景。
九十四、TreeMap
TreeMap:
按照 key 排序
底层基于:
红黑树
key 需要:
自然排序
或 Comparator
九十五、HashMap、LinkedHashMap、TreeMap 对比
| 类型 | 顺序 | 查询 | 典型用途 |
|---|---|---|---|
HashMap |
不保证 | 快 | 默认键值存储 |
LinkedHashMap |
常保留插入顺序 | 快 | 需要顺序 |
TreeMap |
按 key 排序 | 对数级 | 需要有序 key |
九十六、Hashtable
Hashtable 是比较老的 Map 实现。
特点:
很多方法同步
不允许 null key/value
现代普通业务代码中:
使用频率远低于 HashMap
多线程场景通常也会优先考虑:
ConcurrentHashMap
而不是 Hashtable。
九十七、Queue
Queue:
队列
通常强调:
先进先出
英文:
FIFO
First In First Out
九十八、Queue 常见方法
添加:
offer()
取出并删除:
poll()
查看队头:
peek()
九十九、Queue 基础案例
Queue<String> queue =
new LinkedList<>();
queue.offer("A");
queue.offer("B");
queue.offer("C");
System.out.println(
queue.poll()
);
输出:
A
一百、Deque
Deque:
双端队列
两端都可以:
添加
删除
查看
常用实现:
ArrayDeque
LinkedList
一百零一、ArrayDeque
例如:
Deque<String> deque =
new ArrayDeque<>();
可以作为:
队列
栈
使用。
一百零二、用 Deque 实现栈
栈:
后进先出
英文:
LIFO
Last In First Out
例如:
Deque<String> stack =
new ArrayDeque<>();
stack.push("A");
stack.push("B");
stack.push("C");
System.out.println(
stack.pop()
);
输出:
C
一百零三、PriorityQueue
PriorityQueue:
优先队列
不是简单按照插入顺序出队,
而是根据:
优先级 / 排序规则
决定。
例如:
PriorityQueue<Integer> queue =
new PriorityQueue<>();
queue.offer(30);
queue.offer(10);
queue.offer(20);
System.out.println(
queue.poll()
);
结果:
10
一百零四、Collections 工具类
注意:
Collection
是接口。
Collections
是工具类。
它们不是一个东西。
一百零五、Collections.sort()
例如:
List<Integer> list =
new ArrayList<>();
list.add(30);
list.add(10);
list.add(20);
Collections.sort(list);
结果:
10
20
30
一百零六、List.sort()
现代 Java 也可以:
list.sort(
Comparator.naturalOrder()
);
或者:
list.sort(comparator);
一百零七、Collections.reverse()
反转:
Collections.reverse(list);
一百零八、Collections.shuffle()
随机打乱:
Collections.shuffle(list);
适合:
洗牌
随机顺序
一百零九、Collections.max() / min()
Integer max =
Collections.max(list);
Integer min =
Collections.min(list);
一百一十、Collections.swap()
交换两个索引:
Collections.swap(
list,
0,
1
);
一百一十一、Collections.frequency()
统计元素出现次数:
int count =
Collections.frequency(
list,
"Java"
);
一百一十二、Collections.unmodifiableList()
可以创建:
不可修改视图
例如:
List<String> readonly =
Collections
.unmodifiableList(
list
);
对 readonly 做添加:
readonly.add("Redis");
会抛:
UnsupportedOperationException
一百一十三、List.of()
Java 9 以后:
List<String> list =
List.of(
"Java",
"MySQL",
"Spring"
);
得到:
不可变集合
不能:
list.add(...)
一百一十四、Set.of()
Set<String> set =
Set.of(
"Java",
"MySQL"
);
同样:
不可修改
并且重复元素会直接导致创建失败。
一百一十五、Map.of()
例如:
Map<String, Integer> map =
Map.of(
"Java", 90,
"MySQL", 85
);
适合:
少量固定配置
只读数据
一百一十六、不可变集合有什么好处
避免误修改
表达数据只读语义
线程共享更安全
API 返回更稳妥
但要注意:
集合本身不可改
不代表里面对象一定不可变
一百一十七、泛型类
我们也可以自己定义泛型类。
例如:
public class Box<T> {
private T value;
public T getValue() {
return value;
}
public void setValue(
T value
) {
this.value = value;
}
}
使用:
Box<String> box =
new Box<>();
box.setValue("Java");
一百一十八、T 是什么
T 只是一个:
类型参数名字
常见字母:
T Type
E Element
K Key
V Value
例如 Map:
Map<K, V>
一百一十九、泛型方法
public static <T>
void print(
T value
) {
System.out.println(value);
}
调用:
print("Java");
print(100);
一百二十、泛型通配符 ?
例如:
List<?> list
表示:
某种未知类型的 List
它常用于:
只需要读取
不关心具体元素类型
的通用方法。
一百二十一、? extends
例如:
List<? extends Number>
表示:
Number
或 Number 的某个子类型
例如:
List<Integer>
List<Double>
都可以匹配。
一百二十二、? super
例如:
List<? super Integer>
表示:
Integer
或 Integer 的某个父类型
例如:
List<Integer>
List<Number>
List<Object>
一百二十三、PECS 原则
泛型通配符有一个经典口诀:
Producer Extends
Consumer Super
简称:
PECS
可以简单理解:
主要读取数据
→ ? extends
主要写入数据
→ ? super
初学阶段先理解思想,不必一开始钻太深。
一百二十四、泛型不能直接使用基本类型
错误:
List<int>
正确:
List<Integer>
因为 Java 泛型主要基于:
引用类型
一百二十五、类型擦除简单了解
Java 泛型大部分信息:
主要在编译期做类型检查
运行时很多泛型信息会被:
擦除
这叫:
类型擦除
后面学习反射和泛型底层时再深入。
一百二十六、集合中 null 的问题
不同集合对:
null
支持不同。
例如:
ArrayList
HashMap
HashSet
通常允许某种程度的 null。
而:
TreeSet
TreeMap
List.of
Set.of
Map.of
等场景对 null 限制更严格。
实际开发中:
不要依赖大量 null 作为集合中的正常业务值。
一百二十七、集合中的 equals()
很多集合操作:
contains()
remove()
indexOf()
都会涉及:
equals()
例如:
list.contains(student)
是否能找到,
取决于对象:
equals()
如何定义。
一百二十八、Set / Map 更依赖 hashCode()
特别是:
HashSet
HashMap
高度依赖:
hashCode()
equals()
所以面向对象里的:
equals 和 hashCode
不是单纯“面试题”,而是真正影响集合行为。
一百二十九、List 去重的简单方式
例如:
List<String> list =
List.of(
"Java",
"MySQL",
"Java"
);
可以:
Set<String> set =
new HashSet<>(list);
得到去重结果。
如果还想保留顺序:
Set<String> set =
new LinkedHashSet<>(list);
一百三十、Set 转 List
List<String> list =
new ArrayList<>(set);
一百三十一、List 转数组
String[] array =
list.toArray(
new String[0]
);
现代 Java 也可以使用其他形式,
初学阶段先掌握这一种。
一百三十二、数组转 List
例如:
String[] array = {
"Java",
"MySQL"
};
List<String> list =
Arrays.asList(array);
注意:
Arrays.asList()
返回的是固定大小视图
不能随便:
add()
remove()
一百三十三、Arrays.asList() 的坑
例如:
List<String> list =
Arrays.asList(
"Java",
"MySQL"
);
list.add("Redis");
会抛:
UnsupportedOperationException
如果需要可变 ArrayList:
List<String> list =
new ArrayList<>(
Arrays.asList(
"Java",
"MySQL"
)
);
一百三十四、基本类型数组和 Arrays.asList()
例如:
int[] array = {
1, 2, 3
};
List<int[]> list =
Arrays.asList(array);
它不会变成:
List<Integer>
因为:
int[] 本身被当成一个对象
所以基本类型数组转换集合要特别注意。
一百三十五、排序自定义对象
例如 Student:
public class Student {
private String name;
private int age;
private double score;
// Getter / Setter
}
按照年龄排序:
students.sort(
Comparator.comparingInt(
Student::getAge
)
);
这里的方法引用后面会学习。
一百三十六、倒序排序
students.sort(
Comparator
.comparingInt(
Student::getAge
)
.reversed()
);
一百三十七、多字段排序
先按分数降序,
分数相同按年龄升序:
students.sort(
Comparator
.comparingDouble(
Student::getScore
)
.reversed()
.thenComparingInt(
Student::getAge
)
);
这种写法在实际项目里很常见。
一百三十八、集合综合案例:学生管理
Student:
public class Student {
private String studentNo;
private String name;
private int age;
private double score;
public Student(
String studentNo,
String name,
int age,
double score
) {
this.studentNo =
studentNo;
this.name = name;
this.age = age;
this.score = score;
}
public String getStudentNo() {
return studentNo;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public double getScore() {
return score;
}
@Override
public String toString() {
return "Student{"
+ "studentNo='"
+ studentNo
+ '\''
+ ", name='"
+ name
+ '\''
+ ", age="
+ age
+ ", score="
+ score
+ '}';
}
}
一百三十九、使用 List 保存学生
List<Student> students =
new ArrayList<>();
students.add(
new Student(
"001",
"张三",
20,
90
)
);
students.add(
new Student(
"002",
"李四",
21,
85
)
);
一百四十、按学号查找学生
String targetNo =
"002";
Student found = null;
for (
Student student :
students
) {
if (
targetNo.equals(
student.getStudentNo()
)
) {
found = student;
break;
}
}
一百四十一、如果经常按学号查询怎么办
如果业务中大量:
学号 → 学生
查询,
使用:
Map<String, Student>
可能更加合适。
例如:
Map<String, Student> studentMap =
new HashMap<>();
studentMap.put(
"001",
new Student(
"001",
"张三",
20,
90
)
);
查找:
Student student =
studentMap.get("001");
一百四十二、选择集合要根据访问方式
如果需求:
按顺序保存学生
使用:
List<Student>
如果:
学号必须唯一
可以考虑:
Set
或:
Map
如果:
经常通过学号快速找学生
更适合:
Map<String, Student>
一百四十三、综合案例:单词统计
字符串:
java spring java mysql java spring
要求统计每个单词出现次数。
使用:
Map<String, Integer>
代码:
String text =
"java spring java mysql java spring";
String[] words =
text.split(" ");
Map<String, Integer> countMap =
new HashMap<>();
for (String word : words) {
int count =
countMap.getOrDefault(
word,
0
);
countMap.put(
word,
count + 1
);
}
System.out.println(
countMap
);
结果类似:
{java=3, spring=2, mysql=1}
一百四十四、使用 merge() 简化统计
Map 还提供:
merge()
可以写:
for (String word : words) {
countMap.merge(
word,
1,
Integer::sum
);
}
这种写法更简洁,
方法引用后面继续学习。
一百四十五、综合案例:去重并保序
原数据:
List<String> names =
Arrays.asList(
"张三",
"李四",
"张三",
"王五",
"李四"
);
使用:
Set<String> set =
new LinkedHashSet<>(
names
);
得到:
张三
李四
王五
一百四十六、综合案例:排行榜
假设:
用户积分
需要按积分从高到低排序。
可以:
List<User> users =
new ArrayList<>();
然后:
users.sort(
Comparator
.comparingInt(
User::getScore
)
.reversed()
);
如果需要:
排名
再使用普通 for:
for (int i = 0;
i < users.size();
i++) {
User user =
users.get(i);
System.out.println(
"第 "
+ (i + 1)
+ " 名:"
+ user.getName()
);
}
一百四十七、综合案例:购物车
购物车可以用:
Map<Long, CartItem>
其中:
key
商品 ID
value
购物车项
如果重复加入同一个商品:
不新增 key
只更新数量
这就是 Map 在业务中的典型用法。
一百四十八、综合案例:按学院分组学生
例如:
计算机学院
→ 多个学生
外国语学院
→ 多个学生
可以使用:
Map<
String,
List<Student>
>
即:
key
学院名称
value
学生列表
这是非常常见的:
一对多
内存数据结构。
一百四十九、嵌套集合
集合可以嵌套:
List<List<String>>
Map<String, List<Student>>
Map<String, Map<String, Integer>>
真实业务中很常见。
但不要为了“高级”设计得过深。
如果嵌套结构越来越复杂,
通常应该考虑:
定义业务类
让数据结构更清晰。
一百五十、集合和面向对象结合
不推荐大量使用:
Map<String, Object>
乱装业务字段。
例如:
Map<String, Object> user =
new HashMap<>();
然后:
name
age
phone
school
全靠字符串 key。
更好的方式通常是:
User
类。
集合应该主要负责:
保存多个对象
组织对象之间的结构