使用 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():创建不可变空ListemptyMap():创建不可变空MapemptySet():创建不可变空Set- 特点:返回的集合不可修改(添加 / 删除会抛出异常)。
-
空集合(新版 JDK≥9)
- 直接使用
List.of()、Map.of()、Set.of()创建空集合。
- 直接使用
-
单元素集合(旧版 JDK)
singletonList(T o):创建含单个元素的不可变ListsingletonMap(K key, V value):创建含单个键值对的不可变Mapsingleton(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为不可变ListunmodifiableSet(Set<? extends T> set):封装可变Set为不可变SetunmodifiableMap(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),这些同步方法已较少使用。