使用 List
一、List 基础概念
-
定义
List
是 Java 集合框架中的有序列表,元素按插入顺序存储,支持通过索引(从 0 开始)访问。- 类似数组,但提供动态增删功能,避免数组手动扩容和元素移动的繁琐。
-
核心接口方法
- 添加:
add(E e)
(末尾)、add(int index, E e)
(指定位置) - 删除:
remove(int index)
(按索引)、remove(Object e)
(按元素) - 访问:
get(int index)
(获取元素)、size()
(获取大小)
- 添加:
二、List 实现类对比
特性 | ArrayList |
LinkedList |
---|---|---|
数据结构 | 动态数组(基于数组实现) | 双向链表 |
随机访问 | 高效(O (1)) | 低效(需从头遍历,O (n)) |
中间增删 | 需移动元素(O (n)) | 仅修改指针(O (1)) |
内存占用 | 较小(连续存储) | 较大(节点包含前后指针) |
推荐场景 | 频繁随机访问、尾部增删 | 频繁中间增删 |
最佳实践 :优先使用ArrayList
,仅在需要高效中间增删时用LinkedList
。
三、List 特点
-
元素允许性
- 支持重复元素和
null
值(如list.add(null)
合法)。
- 支持重复元素和
-
有序性
- 元素顺序由插入顺序决定,索引稳定。
四、创建 List 的方式
-
常规创建
List<String> list = new ArrayList<>();
(动态数组)List<String> list = new LinkedList<>();
(链表)
-
快速创建只读 List(JDK 9+)
List<Integer> list = List.of(1, 2, 3);
(不可变,不允许null
,否则抛异常)
-
数组转 List
List<Integer> list = Arrays.asList(array);
(JDK 11 前)或List.of(array)
(JDK 11+)。
五、遍历 List 的方法
-
索引遍历(不推荐 LinkedList)
javafor (int i = 0; i < list.size(); i++) { String element = list.get(i); }
-
迭代器遍历(推荐)
javafor (Iterator<String> it = list.iterator(); it.hasNext(); ) { String element = it.next(); }
-
增强 for 循环(语法糖,内部用 Iterator)
javafor (String element : list) { /* 遍历元素 */ }
六、List 与数组的转换
-
List 转数组
- 无类型参数:
Object[] array = list.toArray();
(丢失类型信息) - 带类型参数:
String[] array = list.toArray(new String[0]);
(推荐,自动匹配类型) - 函数式写法(JDK 16+):
String[] array = list.toArray(String[]::new);
- 无类型参数:
-
数组转 List
List<String> list = List.of(array);
(只读)或new ArrayList<>(Arrays.asList(array));
(可修改)
编写 equals 方法
一、List 集合与 equals 方法的关系
-
List 的元素判断机制
List
的contains(Object o)
和indexOf(Object o)
方法通过equals()
而非==
判断元素是否相等。- 示例:即使
new String("C")
与列表中的"C"
是不同实例,仍能正确匹配,因为String
覆写了equals()
。
-
自定义类的问题
- 若自定义类(如
Person
)未覆写equals()
,使用contains()
等方法时会因引用不同而返回错误结果(即使内容相同)。
- 若自定义类(如
二、equals 方法的核心规则(Java 规范)
-
必须满足的 5 大条件
- 自反性 :
x.equals(x)
恒为true
(x≠null
)。 - 对称性 :若
x.equals(y)
为true
,则y.equals(x)
必为true
。 - 传递性 :若
x.equals(y)
且y.equals(z)
为true
,则x.equals(z)
必为true
。 - 一致性 :只要对象状态不变,
equals()
结果恒定。 - 非空性 :
x.equals(null)
恒为false
。
- 自反性 :
三、编写 equals 方法的步骤
-
定义 "相等" 的逻辑
- 确定哪些字段相等时,对象即视为相等(如
Person
的name
和age
)。
- 确定哪些字段相等时,对象即视为相等(如
-
类型检查
- 用
instanceof
判断待比较对象是否为当前类型,若是则转型后比较字段,否则返回false
。
- 用
-
字段比较
- 引用类型字段 :使用
Objects.equals(a, b)
(自动处理null
情况,两者为null
时视为相等)。 - 基本类型字段 :直接用
==
比较(如int age
)。 - 示例代码:
javapublic boolean equals(Object o) { if (o instanceof Person p) { return Objects.equals(this.name, p.name) && this.age == p.age; } return false; }
- 引用类型字段 :使用
四、关键工具与最佳实践
-
简化引用类型比较
- 使用
java.util.Objects.equals()
避免手动处理null
,提升代码简洁性。
- 使用
-
适用场景
- 仅当需要在
List
等集合中使用contains()
、indexOf()
时,才需覆写equals()
;否则可不实现。
- 仅当需要在
Java 中 Map 的使用讲解
一、核心概念
-
Map 数据结构
- 一种键值(key-value)映射表,通过
key
快速查找value
,比List
遍历查找效率更高。 - 接口定义为
Map<K, V>
,常用实现类为HashMap
。
- 一种键值(key-value)映射表,通过
-
基本操作
- 存储 :
put(K key, V value)
,若key
已存在,旧value
会被覆盖,返回旧值;否则返回null
。 - 查询 :
get(K key)
,若key
不存在,返回null
;containsKey(K key)
用于判断key
是否存在。
- 存储 :
二、核心特性
-
键值规则
key
不可重复,重复key
会覆盖原有映射的value
。value
可重复,不同key
可映射到相同value
。
-
无序性
Map
不保证遍历顺序,与插入顺序或排序无关,不同 JDK 版本遍历顺序可能不同,依赖顺序的逻辑不可靠。
三、遍历方法
-
遍历 key
- 通过
keySet()
获取所有key
的Set
集合,使用for each
循环:
javafor (String key : map.keySet()) { ... }
- 通过
-
遍历 key-value
- 通过
entrySet()
获取所有键值对Map.Entry
集合,直接获取key
和value
:
javafor (Map.Entry<String, Integer> entry : map.entrySet()) { String key = entry.getKey(); Integer value = entry.getValue(); }
- 通过
四、注意事项
- 效率优势 :
Map
通过哈希算法实现快速查找,平均时间复杂度为 O (1),远优于List
的 O (n) 遍历。 - null 值处理 :
get()
方法返回null
表示key
不存在,需注意空指针异常(NPE)。
编写 hashCode 方法
一、核心概念:HashMap 与 hashCode 的关系
-
HashMap 的工作原理
- 通过 空间换时间 ,使用数组存储值,根据键的
hashCode()
计算索引直接定位值的存储位置,无需遍历,提升查询效率。 - 示例:键
"a"
计算索引为1
,对应值存储在数组索引1
处。
- 通过 空间换时间 ,使用数组存储值,根据键的
-
键对象的核心要求
-
作为
Map
的键,必须正确覆写equals()
和hashCode()
方法:equals()
用于判断键是否相等(内容相同而非同一对象)。hashCode()
用于计算存储索引,确保相等的键(equals()
返回true
)生成相同的哈希值。
-
二、hashCode 方法的实现规范
-
必须满足的规则
- 规则 1 :若两个对象相等(
a.equals(b) == true
),则它们的hashCode()
必须相等。 - 规则 2 :若两个对象不相等(
a.equals(b) == false
),它们的hashCode()
应尽量不同(减少哈希冲突,提升效率)。
- 规则 1 :若两个对象相等(
-
实现方法
-
基础实现 :对每个参与
equals()
比较的字段计算哈希值,常用31 * h + 字段哈希值
的形式(如字符串、基本类型)。java@Override public int hashCode() { int h = 0; h = 31 * h + firstName.hashCode(); h = 31 * h + lastName.hashCode(); h = 31 * h + age; return h; }
-
简化方案 :使用
Objects.hash(字段1, 字段2, ...)
自动处理null
值,避免NullPointerException
。javareturn Objects.hash(firstName, lastName, age);
-
-
关键原则
equals()
中使用的字段必须全部包含在hashCode()
的计算中,且不能包含未在equals()
中使用的字段。
三、HashMap 深度解析(扩展知识)
-
哈希冲突与解决
- 不同键可能生成相同的
hashCode
(哈希冲突),HashMap 内部通过 链表(或红黑树,JDK 8+) 存储冲突的键值对,冲突越多,查询效率越低(需遍历链表)。
- 不同键可能生成相同的
-
数组扩容机制
- 初始默认容量为 16,通过
hashCode() & (容量 - 1)
计算索引(如容量 16 时,索引范围 0~15)。 - 当元素数量超过阈值(默认容量 × 负载因子 0.75)时,数组扩容为原来的 2 倍(如 16→32),并重新计算所有键的索引,频繁扩容影响性能。
- 优化 :提前指定容量(如
new HashMap<>(10000)
),避免多次扩容(实际容量为大于指定值的最小 2 的幂,如 10000→16384)。
- 初始默认容量为 16,通过
四、最佳实践与注意事项
-
必须同时覆写 equals 和 hashCode
- 若覆写
equals()
而未覆写hashCode()
,会导致HashMap
等集合类无法正确工作(如相等对象被视为不同键)。
- 若覆写
-
提升哈希值质量
- 合理选择参与计算的字段,减少哈希冲突,确保哈希值均匀分布。
-
值对象无特殊要求
HashMap
对存储的value
对象无equals()
和hashCode()
覆写要求,仅针对key
对象。
使用 EnumMap
一、核心定位与优势
-
专属场景
- 当
Map
的键(Key)为 ** 枚举类型(enum
)** 时,EnumMap
是官方推荐的最优实现,专为枚举键设计。
- 当
-
核心优势
- 高效性 :无需计算键的
hashCode()
,直接通过枚举常量的ordinal()
(自然顺序)定位内部数组索引,所有操作(增、删、查)均为 O (1) 时间复杂度。 - 空间紧凑 :内部以数组存储值,无哈希表额外开销(如链表、红黑树),内存占用仅为存储值的必要空间,比
HashMap
更节省内存。 - 类型安全 :键必须为枚举实例,编译期校验合法性,禁止
null
键(枚举常量本身不为null
),值可接受null
。
- 高效性 :无需计算键的
二、基础用法与示例
-
创建与初始化
javaimport java.time.DayOfWeek; import java.util.Map; import java.util.EnumMap; public class Main { public static void main(String[] args) { // 创建EnumMap,指定键的枚举类型为DayOfWeek Map<DayOfWeek, String> map = new EnumMap<>(DayOfWeek.class); // 填充键值对(键必须为枚举常量) map.put(DayOfWeek.MONDAY, "星期一"); // 遍历(按枚举自然顺序,即ordinal()顺序) for (Map.Entry<DayOfWeek, String> entry : map.entrySet()) { System.out.println(entry.getKey() + " → " + entry.getValue()); } } }
-
接口化编程
- 推荐通过
Map
接口引用EnumMap
,遵循 "面向接口编程" 原则,支持与HashMap
等实现无缝替换:
javaMap<EnumType, Value> map = new EnumMap<>(EnumType.class); // 推荐 // 避免紧耦合实现类 // EnumMap<EnumType, Value> map = new EnumMap<>(EnumType.class);
- 推荐通过
三、最佳实践与注意事项
-
适用场景
- 枚举键映射:状态枚举(如订单状态)、类型转换(如枚举到描述 / 配置的映射)、高频访问的枚举键数据。
- 性能敏感场景:需极致优化查找、插入效率时(如核心业务逻辑中的枚举键数据存储)。
-
对比选择
实现类 优势 劣势 适用场景 EnumMap
枚举键专用,O (1) 效率,空间紧凑 仅支持枚举键 键为枚举类型时 HashMap
通用键,平均 O (1) 效率 需计算哈希,可能有哈希冲突 键为非枚举类型时 TreeMap
键有序(自然顺序或定制顺序) O (logn) 效率,红黑树开销 需按键排序时 -
注意事项
- 枚举顺序依赖 :内部数组索引由
ordinal()
决定,枚举常量顺序修改会影响存储顺序,建议枚举定义后避免调整顺序。 - 键合法性 :禁止插入
null
键(会抛出NullPointerException
),值可为null
(与普通Map
一致)。
- 枚举顺序依赖 :内部数组索引由
四、原理与设计思想
-
底层实现
- 基于数组存储,数组长度等于枚举常量的数量,索引直接对应枚举的
ordinal()
值。例如,DayOfWeek.MONDAY
(ordinal()
=0)对应数组索引 0,TUESDAY
(ordinal()
=1)对应索引 1,以此类推。 - 无需哈希计算和冲突处理,直接通过枚举顺序定位,消除了
HashMap
的哈希碰撞和扩容开销。
- 基于数组存储,数组长度等于枚举常量的数量,索引直接对应枚举的
-
设计原则
- 克制设计:仅针对枚举键场景优化,牺牲通用性换取极致性能与空间效率。
- 接口兼容 :完全实现
Map
接口,支持所有Map
操作(如keySet()
、values()
、entrySet()
),行为与普通Map
一致。
使用 TreeMap
一、TreeMap 核心特性
-
有序映射表
- 实现
SortedMap
接口,内部通过红黑树对 Key 进行排序,遍历时按 Key 顺序输出(非插入顺序)。 - 与
HashMap
的区别 :Key 有序(自然排序 / 自定义排序),插入、查询时间复杂度为O(log n)
(HashMap 平均O(1)
)。
- 实现
二、Key的排序规则
- 自然排序(默认)
- 要求 :Key必须实现
Comparable
接口(如String
、Integer
等内置类型已实现)。 - 排序依据 :按
compareTo()
方法定义的顺序排序(如String
字典序、Integer
数值大小)。
- 自定义排序(
Comparator
)
- 适用场景 :Key未实现
Comparable
时,需在创建TreeMap
时传入Comparator
。 compare(a, b)
规则 :a < b
→ 返回负数(如-1
);a == b
→ 必须返回0
(否则导致查找异常);a > b
→ 返回正数(如1
)。
三、典型示例与注意事项
- 自然排序示例
java
Map<String, Integer> map = new TreeMap<>();
map.put("orange", 1);
map.put("apple", 2);
map.put("pear", 3);
// 遍历Key顺序:apple(A)→ orange(O)→ pear(P)(String字典序)
- 自定义排序示例(按
Person.name
排序)
java
Map<Person, Integer> map = new TreeMap<>(
(p1, p2) -> p1.name.compareTo(p2.name)
);
-
错误示例与修正(未处理相等情况)
- 错误代码(导致查找失败):
java// 未处理score相等时返回0,导致TreeMap认为不同Key相等 return p1.score > p2.score ? -1 : 1;
- 正确代码(显式处理相等逻辑):
javareturn p1.score == p2.score ? 0 : (p1.score > p2.score ? -1 : 1); // 或使用Integer.compare(p1.score, p2.score)自动处理
-
Key 的特殊性
- 不依赖
equals()
和hashCode()
:TreeMap 通过比较逻辑(Comparable
/Comparator
)判断 Key 是否相等(返回0
时视为相等,后续 Key 会覆盖前一个)。
- 不依赖
四、核心注意事项
-
比较逻辑的严谨性
- 必须严格遵循
compare()
规范,尤其是相等时返回0
,否则会导致排序异常或查找失败(如示例中Student
类因未处理相等情况导致get()
返回null
)。
- 必须严格遵循
-
适用场景
- 推荐场景:需要按 Key 顺序遍历(如统计、排序展示)。
- 性能 :红黑树实现,插入、删除、查询时间复杂度均为
O(log n)
,适合中等规模数据。
使用 Set
一、Set 的核心概念
-
定义
-
Set
是 Java 集合框架中的接口,用于存储不重复元素 的集合,相当于仅存储Map
的 key(value 为固定占位符)。 -
核心方法:
add(E e)
:添加元素(成功返回true
,重复则返回false
)。remove(Object e)
:删除元素。contains(Object e)
:判断是否包含元素。size()
:获取元素数量。
-
-
与 Map 的关系
HashSet
内部基于HashMap
实现,每个元素作为HashMap
的 key,value 为固定对象PRESENT
。
二、主要实现类
实现类 | 有序性 | 数据结构 | 元素要求 | 特点 |
---|---|---|---|---|
HashSet |
无序 | 哈希表(HashMap) | 正确实现equals() 和hashCode() |
查找效率高,遍历时顺序不确定(非插入顺序,非排序顺序)。 |
TreeSet |
有序 | 红黑树(TreeMap) | 实现Comparable 接口或传入Comparator |
遍历时按元素自然顺序(如 String 字典序)或自定义排序,适合需要有序存储的场景。 |
三、核心特性与使用场景
-
去重功能
-
直接利用
Set
的不重复性去除重复元素,例如从List
中去重:javaList<String> list = new ArrayList<>(Arrays.asList("a", "b", "a")); Set<String> set = new HashSet<>(list); // 自动去重
-
-
元素要求
- HashSet / 普通 Set :元素需正确重写
equals()
和hashCode()
,否则无法正确判断重复。 - TreeSet :元素需实现
Comparable
接口(自然排序),或在创建时传入Comparator
(自定义排序)。
- HashSet / 普通 Set :元素需正确重写
-
遍历顺序
HashSet
:无序,遍历顺序不可预测。TreeSet
:有序,按自然顺序或Comparator
定义的顺序遍历。
四、示例代码
-
HashSet 基本操作
csharpSet<String> set = new HashSet<>(); set.add("apple"); set.add("banana"); System.out.println(set.contains("apple")); // true set.remove("banana");
-
TreeSet 有序存储
csharpSet<String> sortedSet = new TreeSet<>(); // 自然排序(字典序) sortedSet.add("z"); sortedSet.add("a"); for (String s : sortedSet) { // 输出:a, z System.out.println(s); }
五、练习:消息去重(按 sequence)
-
需求 :接收重复消息,按
sequence
字段去重,保留首次出现的消息。 -
解决方案:
-
方法 1:利用 HashSet 存储 sequence
javaSet<Integer> seen = new HashSet<>(); List<Message> unique = new ArrayList<>(); for (Message msg : received) { if (seen.add(msg.sequence)) { // 仅添加新sequence的消息 unique.add(msg); } }
-
方法 2:自定义 Message 的 equals/hashCode
重写
equals()
(比较 sequence)和hashCode()
,直接将 Message 对象存入 HashSet:java@Override public boolean equals(Object o) { if (o instanceof Message m) { return sequence == m.sequence; } return false; } @Override public int hashCode() { return Integer.hashCode(sequence); } Set<Message> set = new HashSet<>(received); // 自动去重
-
方法 3:TreeSet 排序去重
传入
Comparator
按 sequence 排序并去重:javaSet<Message> set = new TreeSet<>(Comparator.comparingInt(m -> m.sequence)); set.addAll(received); // 自动去重并排序
-
Java 中 Queue 的使用
一、Queue 基础概念
-
定义
- 一种先进先出(FIFO)的有序集合,仅允许在队尾添加元素 、队首取出元素。
- 与
List
的区别:List
支持任意位置操作,而Queue
限定操作位置(队尾 / 队首)。
-
核心特性
- 部分实现类有容量限制,部分无(如
LinkedList
无界)。 - 避免向队列中添加
null
,否则难以区分 "队列为空" 和 "取出null
元素"。
- 部分实现类有容量限制,部分无(如
二、Queue 核心方法
操作类型 | 抛异常(失败时) | 返回特殊值(失败时) | 说明 |
---|---|---|---|
添加元素 | add(E e) |
offer(E e) |
向队尾添加元素;add 在队列满时抛IllegalStateException ,offer 返回false |
获取并删除队首 | remove() |
poll() |
取出队首元素并删除;队列为空时,remove 抛NoSuchElementException ,poll 返回null |
获取队首元素 | element() |
peek() |
取出队首元素但不删除;队列为空时,element 抛NoSuchElementException ,peek 返回null |
三、方法对比与示例
-
添加元素
add()
:适合明确队列容量足够时使用,否则需捕获异常。
javatry { q.add("Apple"); // 失败时抛异常 } catch (IllegalStateException e) { // 处理异常 }
offer()
:适合容量不确定时,通过返回值判断是否添加成功。
javaif (q.offer("Apple")) { // 添加成功 } else { // 添加失败 }
-
获取并删除队首(
poll()
vsremove()
)poll()
:安全获取元素,避免异常,常用于循环处理队列元素。
javawhile ((element = q.poll()) != null) { // 处理元素 }
remove()
:强制删除,需确保队列非空。
-
获取队首但不删除(
peek()
vselement()
)peek()
:查看队首元素(不改变队列状态),队列空时返回null
。element()
:功能同peek()
,但队列空时抛异常。
四、实现类与最佳实践
-
典型实现类
LinkedList
:实现Queue
接口,无界队列,适合通用场景。
javaQueue<String> queue = new LinkedList<>(); // 面向接口编程
- 其他实现:
PriorityQueue
(优先队列)、ArrayBlockingQueue
(有界阻塞队列)等。
-
编程原则
- 面向抽象编程 :通过
Queue
接口引用实现类(如LinkedList
),提高代码灵活性。 - 避免
null
元素 :防止poll()
返回null
时无法区分 "队列为空" 或 "有效null
元素"。
- 面向抽象编程 :通过
使用 PriorityQueue
一、核心概念
-
优先队列(PriorityQueue)
- 与普通
Queue
(FIFO)不同,PriorityQueue
的出队顺序由元素的优先级 决定,调用remove()
或poll()
时返回优先级最高的元素。 - 应用场景:如 VIP 插队场景,优先级高的元素(如 VIP 客户)可优先出队。
- 与普通
二、基本用法
-
默认排序(自然顺序)
- 元素需实现
Comparable
接口,PriorityQueue
按自然顺序排序。 - 示例:
javaQueue<String> q = new PriorityQueue<>(); q.offer("apple"); q.offer("pear"); q.offer("banana"); // 输出顺序:apple(优先级最高)→ banana → pear(按字符串字典序) System.out.println(q.poll()); // apple
- 元素需实现
-
自定义排序(通过 Comparator)
- 若元素未实现
Comparable
,可通过Comparator
指定排序规则。 - 示例(银行 VIP 优先级) :
javaQueue<User> q = new PriorityQueue<>(new UserComparator()); // VIP(V开头)优先级高于普通客户(A开头),同类型按号码顺序 class UserComparator implements Comparator<User> { public int compare(User u1, User u2) { char c1 = u1.number.charAt(0), c2 = u2.number.charAt(0); if (c1 != c2) return c1 == 'V' ? -1 : 1; // V开头优先级高 return u1.number.compareTo(u2.number); // 同类型按号码排序 } }
- 若元素未实现
三、关键特性
-
元素要求
- 要么实现
Comparable
接口(默认排序),要么提供Comparator
(自定义排序)。
- 要么实现
-
常用方法
offer()
:添加元素;poll()
:取出并移除优先级最高的元素(队列为空时返回null
);peek()
:获取但不移除队首元素。
-
注意事项
- 自定义
Comparator
需确保逻辑正确性,例如避免号码如A10
排在A2
前的错误(需按数值而非字典序比较)。
- 自定义
四、小结
- 核心区别 :
PriorityQueue
按优先级出队,而非严格 FIFO。 - 排序方式 :支持
Comparable
自然排序或Comparator
自定义排序。 - 适用场景:需要根据优先级动态处理元素的场景,如任务调度、实时数据处理等。
Java 中 Deque 接口的使用
一、Deque 基础概念
-
定义
- 双端队列(Double Ended Queue),允许从队列两端(队首、队尾)进行元素的添加和删除操作。
- 接口扩展自
Queue
,是 Java 集合框架的一部分。
-
核心功能
- 元素添加:可从队首或队尾插入元素。
- 元素获取:可从队首或队尾获取并删除元素,或仅查看元素(不删除)。
二、与 Queue 的方法对比
操作类型 | Queue(单端队列) | Deque(双端队列) |
---|---|---|
添加到队尾 | add(E e) / offer(E e) |
addLast(E e) / offerLast(E e) (等效于 Queue 方法) |
添加到队首 | 无 | addFirst(E e) / offerFirst(E e) |
获取并删除队首 | remove() / poll() |
removeFirst() / pollFirst() |
获取并删除队尾 | 无 | removeLast() / pollLast() |
查看队首(不删除) | element() / peek() |
getFirst() / peekFirst() (等效于 Queue 方法) |
查看队尾(不删除) | 无 | getLast() / peekLast() |
三、常用方法与最佳实践
-
推荐方法
- 添加元素 :明确使用
offerFirst()
(队首)或offerLast()
(队尾),避免直接调用offer()
(默认队尾,易混淆)。 - 删除元素 :
pollFirst()
(队首删除)、pollLast()
(队尾删除),返回null
而非抛出异常(安全)。 - 查看元素 :
peekFirst()
(队首)、peekLast()
(队尾),安全获取元素(不删除,返回null
而非抛出异常)。
- 添加元素 :明确使用
-
实现类
ArrayDeque
:基于数组实现,性能高效,不支持null
元素,适合频繁两端操作。LinkedList
:基于链表实现,同时实现List
、Queue
、Deque
接口,支持null
元素,但性能略逊于ArrayDeque
。
-
编码规范
- 面向接口编程 :通过
Deque
接口引用实现类(如Deque<String> deque = new LinkedList<>();
),而非直接使用LinkedList
,提升抽象层次。 - 避免
null
元素 :多数实现类(如ArrayDeque
)不支持null
,添加null
可能导致异常。
- 面向接口编程 :通过
四、示例代码
java
import java.util.Deque;
import java.util.LinkedList;
public class Main {
public static void main(String[] args) {
Deque<String> deque = new LinkedList<>();
// 添加元素(队尾、队首)
deque.offerLast("A"); // 队尾添加"A"
deque.offerFirst("C"); // 队首添加"C",当前队列:C -> A
// 删除元素(队首、队尾)
System.out.println(deque.pollFirst()); // 输出"C",剩余:A
System.out.println(deque.pollLast()); // 输出"A",剩余:空
// 安全获取元素(不删除)
System.out.println(deque.peekFirst()); // 输出null(队列空)
}
}
使用 Stack
一、Stack 基础概念
-
数据结构特性
- 后进先出(LIFO):最后压入栈的元素最先弹出,与队列的 FIFO(先进先出)特性形成对比。
- 核心操作:
push(E)
(压栈)、pop()
(弹出栈顶元素)、peek()
(获取栈顶元素不弹出)。
-
直观示例
通过 ASCII 图示演示栈的压入和弹出过程,形象化 LIFO 机制。
二、Java 中 Stack 的实现
-
推荐使用 Deque 接口
Deque
(双端队列)模拟 Stack 功能,常用方法:
push(E)
/addFirst(E)
(压栈);pop()
/removeFirst()
(弹出);peek()
/peekFirst()
(查看栈顶)。- 最佳实践 :仅调用
push()
/pop()
/peek()
,避免使用 Deque 的其他方法,保持代码清晰。
-
避免使用遗留类
Stack
因 Java 早期设计遗留的
Stack
类兼容性问题,推荐使用 Deque 替代。
三、Stack 的典型应用场景
-
JVM 方法调用栈
- 维护方法调用层次:参数压栈、返回值压栈,嵌套调用过深会引发
StackOverflowError
(如无限递归)。
- 维护方法调用层次:参数压栈、返回值压栈,嵌套调用过深会引发
-
进制转换(以十进制转十六进制为例)
- 步骤:循环取余压栈,直至商为 0,再依次弹出栈元素组成结果字符串。
- 示例:
12500
转十六进制,通过反复除以 16 取余,最终弹出栈得到30D4
。
-
中缀表达式编译与计算
- 中缀转后缀 :利用栈处理运算符优先级和括号,生成后缀表达式(如
1 + 2*(9-5)
→1 2 9 5 - * +
)。 - 后缀表达式计算:从左到右扫描,遇数字压栈,遇运算符弹出操作数计算后压栈,最终栈顶为结果。
- 中缀转后缀 :利用栈处理运算符优先级和括号,生成后缀表达式(如
Java 迭代器(Iterator)使用详解
一、核心概念
-
Iterator 本质
-
是 Java 集合遍历的统一抽象模型,
for each
循环本质上由编译器转换为 Iterator 遍历。 -
示例:
for (String s : list)
会被编译为:javafor (Iterator<String> it = list.iterator(); it.hasNext(); ) { String s = it.next(); }
-
-
核心接口
Iterable
:集合类需实现此接口(定义iterator()
方法),以支持for each
循环。Iterator
:定义遍历逻辑,包含hasNext()
(判断是否有下一个元素)和next()
(获取下一个元素)方法。
二、Iterator 优势
-
统一访问方式
调用方无需关心集合内部存储结构(如
ArrayList
的数组、LinkedList
的链表),以相同代码遍历List
、Set
、Queue
等。 -
解耦与通用性
- 避免依赖集合特定接口(如
get(int)
),例如Set
无索引,无法用索引遍历,但可统一用 Iterator。 - 提升代码复用性,更换集合类型时无需修改遍历逻辑。
- 避免依赖集合特定接口(如
-
高效性
集合内部创建的 Iterator 可针对自身结构优化遍历(如
LinkedList
的 Iterator 避免低效的get(int)
)。
三、自定义集合使用 Iterator
若需让自定义集合支持 for each
循环,需满足:
-
实现
Iterable
接口- 重写
iterator()
方法,返回自定义的Iterator
实例。
- 重写
-
实现
Iterator
接口- 在内部类中实现
hasNext()
和next()
,定义具体遍历逻辑(如倒序遍历)。
- 在内部类中实现
示例代码
java
class ReverseList<T> implements Iterable<T> {
private List<T> list = new ArrayList<>();
// 实现 Iterable 接口
@Override
public Iterator<T> iterator() {
return new ReverseIterator(list.size()); // 返回自定义 Iterator
}
// 内部类实现 Iterator 接口(倒序遍历)
class ReverseIterator implements Iterator<T> {
int index;
ReverseIterator(int index) { this.index = index; }
@Override public boolean hasNext() { return index > 0; }
@Override public T next() { return list.get(--index); }
}
}
四、关键要点
-
for each
的本质依赖
Iterable
和Iterator
接口,编译器自动生成遍历代码。 -
分离职责
集合类负责存储数据,Iterator 负责遍历逻辑,调用方只需关注业务逻辑。
-
适用场景
所有 Java 集合类(
List
、Set
、Queue
、Map
的键 / 值)均支持 Iterator,推荐优先使用以提升代码健壮性。
使用 Collections
一、Collections 工具类概述
- 定位 :JDK 提供的工具类,位于
java.util
包,提供一系列操作集合的静态方法。 - 注意 :名称为
Collections
(结尾有s
),与Collection
接口不同。
二、核心功能与方法
1. 创建特殊集合
-
空集合(旧版 JDK)
emptyList()
:创建不可变空List
emptyMap()
:创建不可变空Map
emptySet()
:创建不可变空Set
- 特点:返回的集合不可修改(添加 / 删除会抛出异常)。
-
空集合(新版 JDK≥9)
- 直接使用
List.of()
、Map.of()
、Set.of()
创建空集合。
- 直接使用
-
单元素集合(旧版 JDK)
singletonList(T o)
:创建含单个元素的不可变List
singletonMap(K key, V value)
:创建含单个键值对的不可变Map
singleton(T o)
:创建含单个元素的不可变Set
- 新版替代 :
List.of(T...)
、Map.of(T...)
、Set.of(T...)
支持创建任意数量元素的不可变集合。
2. 集合操作
-
排序
- 方法:
Collections.sort(List<T> list)
,直接修改传入的可变List
,按自然顺序或自定义比较器排序。
- 方法:
-
洗牌(随机打乱顺序)
- 方法:
Collections.shuffle(List<T> list)
,随机重排List
元素顺序。
- 方法:
3. 不可变集合封装
-
方法
unmodifiableList(List<? extends T> list)
:封装可变List
为不可变List
unmodifiableSet(Set<? extends T> set)
:封装可变Set
为不可变Set
unmodifiableMap(Map<? extends K, ? extends V> m)
:封装可变Map
为不可变Map
-
特性
- 通过代理对象拦截修改操作,禁止对封装后的集合执行增删改操作(会抛出
UnsupportedOperationException
)。 - 原始集合的修改会影响封装后的 "不可变" 集合,建议封装后丢弃原始集合引用。
- 通过代理对象拦截修改操作,禁止对封装后的集合执行增删改操作(会抛出
4. 线程安全集合(过时方法)
-
方法
synchronizedList(List<T> list)
:将List
转为线程安全版本synchronizedSet(Set<T> s)
:将Set
转为线程安全版本synchronizedMap(Map<K,V> m)
:将Map
转为线程安全版本
-
现状 :Java 5 后引入更高效的并发集合类(如
ConcurrentHashMap
),这些同步方法已较少使用。