Java 学习笔记 - 集合与 Stream
Java 学习笔记 - 集合与 Stream
一、集合框架整体架构
两大分支
▼text复制代码Collection 接口(单列集合,存单个元素) ↑ ├─ List 接口(有序、可重复、有下标) │ ├─ ArrayList 实现类 │ └─ LinkedList 实现类 ├─ Set 接口(不可重复) │ ├─ HashSet │ └─ TreeSet └─ Queue / Deque 队列接口 └─ LinkedList 同时实现 List + Deque Map 接口(双列集合,key-value,不属于 Collection) ├─ HashMap 实现类 ├─ TreeMap └─ LinkedHashMap
Map 不属于 Collection,是独立分支!
- Collection:存单个对象
- Map:存键值对(key → value)
LinkedList 多态引用场景
核心要点:引用类型决定编译期能调用哪些方法,对象本身具备全部能力。
▼java复制代码// 场景1:把 LinkedList 当作普通 List 使用 List<String> list = new LinkedList<>(); // 可调用 List 的 add/get/remove 等方法 // 不能直接调用队列 offer/poll,如需使用要强制转型 // 适合:有序列表,频繁中间增删,不需要队列功能 // 场景2:使用队列、双端队列、栈 Deque<String> deque = new LinkedList<>(); // 可直接调用 offer/poll/push/pop // 不能直接调用 get(index) // 适合:消息排队、栈逻辑、头尾频繁增删
Collection 分支:抽丝剥茧的继承链
▼text复制代码Iterable 接口 ↑ extends Collection 接口 ↑ extends List 接口 ↑ implements AbstractCollection 抽象类 ↑ extends AbstractList 抽象类 ↑ extends ArrayList 实现类
| 层级 | 角色 | 职责 |
|---|---|---|
| 接口(Iterable/Collection/List) | 立法者 | 定义规范(方法签名),不实现 |
| 抽象类(AbstractCollection/AbstractList) | 包工头 | 实现通用逻辑,留下差异化部分 |
| 具体类(ArrayList/LinkedList) | 工人 | 完成具体实现 |
为什么这样设计? 新增一种列表只需要 extends AbstractList,重写几个方法就行。
Map 分支:抽丝剥茧的继承链
▼text复制代码Map 接口 ↑ implements AbstractMap 抽象类 ↑ extends HashMap 实现类
二、ArrayList
继承与实现关系
▼java复制代码public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, Serializable
核心方法表
| 方法 | 作用 | 补充说明 | JDK |
|---|---|---|---|
add(E e) | 末尾添加元素 | 返回 boolean,永远 true | 1.2 |
add(int index, E element) | 指定位置插入 | 后面元素全部后移,效率低 | 1.2 |
get(int index) | 根据下标获取 | 越界抛 IndexOutOfBoundsException | 1.2 |
set(int index, E element) | 修改指定下标元素 | 返回旧值 | 1.2 |
remove(int index) | 按下标删除 | 返回被删除元素 | 1.2 |
remove(Object o) | 删除匹配的元素(第一个) | 返回 boolean | 1.2 |
contains(Object o) | 判断是否包含 | 底层调用 equals() | 1.2 |
indexOf(Object o) | 查找首次出现下标 | 找不到返回 -1 | 1.2 |
lastIndexOf(Object o) | 查找最后一次出现下标 | 找不到返回 -1 | 1.2 |
size() | 获取元素个数 | 1.2 | |
isEmpty() | 判断是否为空 | 1.2 | |
clear() | 清空全部元素 | 1.2 | |
sort(Comparator) | 按比较器排序 | JDK 8+ | 1.8 |
经典代码
三种遍历方式:
▼java复制代码// 需要序号 → 用索引 for (int i = 0; i < workers.size(); i++) { System.out.println((i + 1) + ". " + workers.get(i).getName()); } // 纯遍历 → for-each(推荐) for (Worker w : workers) { System.out.println(w.getName()); } // 过滤统计 → Stream workers.stream() .filter(w -> w.getScore() > 4.0) .forEach(w -> System.out.println(w.getName()));
排序:
▼java复制代码// 按工龄升序 workers.sort((a, b) -> a.getWorkYear() - b.getWorkYear()); // 按评分降序 workers.sort((a, b) -> Double.compare(b.getScore(), a.getScore())); // 先按工种,再按工龄降序 workers.sort((a, b) -> { int jobCompare = a.getJobType().compareTo(b.getJobType()); if (jobCompare != 0) return jobCompare; return b.getWorkYear() - a.getWorkYear(); });
注意点
- 面向接口编程:声明用
List<String>,不绑死ArrayList<String> - 遍历方式按场景选:需要序号用 index,纯遍历用 for-each,过滤统计用 Stream
常见坑
-
遍历时直接删除元素 → ConcurrentModificationException
▼java复制代码// ❌ 错误 for (Worker w : workers) { if (w.getScore() < 3.0) workers.remove(w); } // ✅ 正确 workers.removeIf(w -> w.getScore() < 3.0); -
remove(Object)和remove(int)重载陷阱▼java复制代码List<Integer> nums = new ArrayList<>(); nums.add(1); nums.add(2); nums.add(3); nums.remove(1); // ❌ 按索引删,删掉的是 2 nums.remove(Integer.valueOf(1)); // ✅ 按对象删,删掉的是 1 -
扩容性能消耗:默认初始容量 10,满了扩容为 1.5 倍
-
线程不安全:多线程并发 add/remove 可能丢数据
-
subList 是原集合视图:修改 subList 会影响原集合
号外
- ArrayList vs LinkedList:ArrayList 查询快 O(1)、中间增删慢;LinkedList 查询慢 O(n)、首尾增删快
- Vector 已淘汰:线程安全但已过时,不要用
三、HashMap
继承与实现关系
▼java复制代码public class HashMap<K,V> extends AbstractMap<K,V> implements Map<K,V>, Cloneable, Serializable
核心方法表
| 方法 | 作用 | 补充说明 | JDK |
|---|---|---|---|
put(K key, V value) | 添加/覆盖键值对 | key 重复则覆盖旧 value,返回旧 value | 1.2 |
get(Object key) | 根据 key 取值 | key 不存在返回 null | 1.2 |
getOrDefault(K key, V defaultValue) | 取值带默认值 | key 不存在返回默认值 | 1.8 |
remove(Object key) | 根据 key 删除 | 返回被删除的 value | 1.2 |
containsKey(Object key) | 判断是否存在 key | 1.2 | |
containsValue(Object value) | 判断是否存在 value | 1.2 | |
size() | 获取键值对数量 | 1.2 | |
isEmpty() | 判断是否为空 | 1.2 | |
clear() | 清空所有键值对 | 1.2 | |
keySet() | 获取全部 key 集合 | 返回 Set<K> | 1.2 |
values() | 获取全部 value 集合 | 返回 Collection<V> | 1.2 |
entrySet() | 获取全部键值对 | 返回 Set<Map.Entry<K,V>> | 1.2 |
经典代码
四种遍历方式:
▼java复制代码// entrySet 遍历(最常用,key-value 都要) for (Map.Entry<String, Worker> entry : map.entrySet()) { System.out.println(entry.getKey() + "=" + entry.getValue().getName()); } // keySet 遍历(只要 key) for (String key : map.keySet()) { Worker w = map.get(key); } // values 遍历(只要 value) for (Worker w : map.values()) { System.out.println(w.getName()); } // Lambda forEach map.forEach((k, v) -> System.out.println(k + ":" + v.getName()));
存在性判断(项目规范写法):
▼java复制代码public static void findById(String id) { if (!map.containsKey(id)) { System.out.println("不存在:" + id); return; } System.out.println(map.get(id)); }
注意点
- Map 不属于 Collection:Collection 存单个元素,Map 存键值对
- containsKey 比
get() != null更准确:value 可能本身就是 null - HashMap 和 ArrayList 互补配合:ArrayList 有序遍历,HashMap 快速查询
常见坑
- 自定义对象作为 key,必须重写
equals()和hashCode() - 线程不安全:并发 put 导致数据混乱,并发场景改用
ConcurrentHashMap - 无序:遍历顺序 ≠ 存入顺序,需要顺序用
LinkedHashMap - 扩容触发 rehash:元素超过阈值时扩容为 2 倍,所有元素重新计算 hash 位置
号外
- JDK 8 优化:链表长度超过 8 且数组长度超过 64 时,链表转为红黑树
- 负载因子默认 0.75:容量 16 时,元素达到 12 就扩容
- HashMap vs Hashtable:Hashtable 线程安全但已废弃,ConcurrentHashMap 是并发首选
四、Collections 工具类
Collections(带 s)是工具类,Collection(不带 s)是接口。两者完全不同!
核心方法表
| 方法 | 作用 | 示例 |
|---|---|---|
sort(List, Comparator) | 按比较器排序 | Collections.sort(list, (a,b)->a-b) |
sort(List) | 按自然顺序排序 | Collections.sort(stringList) |
reverse(List) | 反转列表顺序 | Collections.reverse(list) |
shuffle(List) | 随机打乱顺序 | Collections.shuffle(list) |
max(Collection, Comparator) | 取最大值 | Collections.max(list, comparator) |
min(Collection, Comparator) | 取最小值 | Collections.min(list, comparator) |
frequency(Collection, Object) | 统计某元素出现次数 | Collections.frequency(list, "a") |
binarySearch(List, Object) | 二分查找(列表必须先排序) | Collections.binarySearch(list, key) |
注意点
-
Collections.sort()vsList.sort()▼java复制代码Collections.sort(workers, comparator); // JDK 1.2 workers.sort(comparator); // JDK 8,两者等价 -
Collections只操作已有集合,不创建新集合 -
sort会修改原列表,不是返回新列表
五、Stream API
基础规则
▼text复制代码元素 -> 逻辑
- 箭头左边:遍历出来的每一个元素
- 箭头右边:执行逻辑
- 单行表达式:不用写 return
- 大括号
{}多行代码块:必须写 return
Stream 操作汇总对比表
| 方法 | Lambda 返回值 | 作用 |
|---|---|---|
filter | boolean | 筛选元素,保留 true |
map | 任意对象 | 一对一转换元素 |
mapToInt | int | 元素转为 int,做 sum/average/max/min |
mapToLong | long | 元素转为 long,做 sum/average/max/min |
mapToDouble | double | 元素转为 double,做 sum/average/max/min |
flatMap | Stream | 一对多,扁平化拆集合 |
sorted | int(Comparator) | 排序 |
distinct | 无 | 去重 |
groupingBy | 任意对象(作为 Map 的 key) | 按 key 分组,相同 key 归为一组 |
partitioningBy | boolean | 真假二分区,只分成两组 |
核心区别:
filter输出 boolean → 决定留不留mapToXxx输出数字 → 做统计运算map输出任意对象 → 元素转换flatMap输出 Stream → 拆内层集合
两大操作类型
| 类型 | 特征 | 例子 |
|---|---|---|
| 中间操作 | 返回 Stream,可无限链式调用 | filter()、map()、sorted() |
| 终结操作 | 返回非 Stream 结果,触发执行 | forEach()、count()、sum()、collect() |
核心规则:没有终结操作,中间操作全部白写!
filter 详解
作用:按条件筛选元素,返回 true 的保留,返回 false 的丢弃。元素本身不变。
函数式接口:Predicate<T> —— 接收一个对象,返回 boolean。
语法拆分:item -> item.getAmount() > 30000
| 部分 | 说明 |
|---|---|
item | 遍历得到的每个流元素,自定义名字 |
-> | Lambda 箭头 |
item.getAmount() > 30000 | 判断逻辑,必须返回 boolean |
两种写法:
▼java复制代码// 单行简写(不用 return) long count = quotations.stream() .filter(q -> q.calcOriginalTotalAmount() > 30000) .count(); // 大括号多行(必须写 return) .filter(q -> { double amount = q.calcOriginalTotalAmount(); return amount > 30000 && amount < 100000; })
多个 filter 串联(且关系):
▼java复制代码quotations.stream() .filter(q -> q.calcOriginalTotalAmount() > 30000) .filter(q -> q.getTotalDiscountRate() < 0.9) .forEach(q -> System.out.println(q.getSummaryLine())); // 等价于一个 filter 用 && 连接 .filter(q -> q.calcOriginalTotalAmount() > 30000 && q.getTotalDiscountRate() < 0.9)
常见坑:
-
Lambda 右边必须返回 boolean,不能返回 double 或其他类型
▼java复制代码.filter(q -> q.getAmount()) // ❌ 返回 double,编译错误 .filter(q -> q.getAmount() > 30000) // ✅ 返回 boolean -
filter 不会修改原集合
▼java复制代码quotations.stream().filter(...); // quotations 本身没变! -
空结果不报错,只是没有元素进入后续步骤
▼java复制代码long count = quotations.stream() .filter(q -> q.getAmount() > 999999999) .count(); // 结果:0,不报错
map 详解
作用:一对一映射。流里 1 个元素 → 输出 1 个新元素。Lambda 返回任意类型。
和 mapToXxx 区分:
mapToXxx产出基础类型流(IntStream/LongStream/DoubleStream)- 普通
map产出Stream<T>(包装类 / 对象流)
示例:
▼java复制代码// 将 Product 转为商品名称字符串 List<String> nameList = productList.stream() .map(p -> p.getName()) .collect(Collectors.toList()); // 多行写法 .map(p -> { String name = p.getName() + "_商品"; return name; })
flatMap 详解
作用:一对多 + 扁平化。1 个元素能拆成多个元素,把内层集合"摊开"。
Lambda 返回:Stream 流(重点!)
场景:元素里面包含集合,需要把里面集合全部拆出来变成一整条流。
示例:订单里包含多个商品
▼java复制代码class Order { private String orderNo; private List<Product> productList; // 一个订单包含多个商品 public List<Product> getProductList() { return productList; } } // 把所有订单里面全部商品提取出来,合并成一个商品流 List<Product> allProducts = orderList.stream() .flatMap(order -> order.getProductList().stream()) .collect(Collectors.toList());
map 和 flatMap 直观对比:
| 方法 | Lambda 返回 | 映射关系 | 核心作用 |
|---|---|---|---|
map | 任意对象 | 一对一 | 元素转换,不拆集合 |
flatMap | Stream<R> | 一对多 + 扁平化 | 拆内层集合,摊平流 |
▼java复制代码// map:返回 Stream<List<Product>>,流里放的是一个个 List,没有摊开 // 结构:[List1, List2, List3] orderList.stream() .map(order -> order.getProductList()) // flatMap:返回 Stream<Product>,把 List 拆开,所有商品全部摊平 // 结构:商品1, 商品2, 商品3... orderList.stream() .flatMap(order -> order.getProductList().stream())
常见坑:
- flatMap 的 Lambda 必须返回 Stream,不能直接返回 List!
▼java复制代码
❌ .flatMap(o -> o.getProductList()) // 返回 List,编译错误 ✅ .flatMap(o -> o.getProductList().stream()) // 返回 Stream
mapToDouble 详解
作用:把 Stream<对象> 转成 DoubleStream(基础类型流),才能调用 sum()、average()、max()、min() 等数学运算。
函数式接口:ToDoubleFunction<T> —— 接收一个对象,返回 double。
语法拆分:item -> item.getPrice()
| 部分 | 说明 |
|---|---|
item | 遍历得到的每个流元素 |
-> | Lambda 箭头 |
item.getPrice() | 转换逻辑,必须返回 double |
三种基础类型流对比:
| 方法 | 函数式接口 | 返回流类型 | Lambda 返回要求 |
|---|---|---|---|
mapToInt | ToIntFunction | IntStream | int |
mapToLong | ToLongFunction | LongStream | long |
mapToDouble | ToDoubleFunction | DoubleStream | double |
示例:
▼java复制代码// 提取金额求和 double total = orderList.stream() .mapToDouble(order -> order.getPrice()) .sum(); // 多行写法(打8折) double discountSum = orderList.stream() .mapToDouble(order -> { double originPrice = order.getPrice(); double after = originPrice * 0.8; return after; }) .sum(); // filter + mapToDouble 搭配 double sum = orderList.stream() .filter(order -> order.getPrice() > 100) .mapToDouble(order -> order.getPrice()) .sum();
DoubleStream 专属终端方法:
| 方法 | 返回类型 | 作用 |
|---|---|---|
.sum() | double | 求和 |
.max() | OptionalDouble | 最大值 |
.min() | OptionalDouble | 最小值 |
.average() | OptionalDouble | 平均值 |
安全取值(OptionalDouble):
▼java复制代码double avg = quotations.stream() .mapToDouble(q -> q.calcOriginalTotalAmount()) .average() .orElse(0); // 如果流为空,返回 0
基础流转回包装类流:
▼java复制代码Stream<Integer> boxedStream = intStream.boxed(); // IntStream → Stream<Integer>
常见坑:
-
Lambda 右边必须返回 double,不能返回 boolean
▼java复制代码.mapToDouble(q -> q.getAmount() > 30000) // ❌ 返回 boolean,编译错误 .mapToDouble(q -> q.getAmount()) // ✅ 返回 double -
mapToDouble 之后得到的是
DoubleStream,不是Stream<Double>Stream<Double>没有sum()方法DoubleStream才有sum()、average()
-
不会修改原集合元素,只是读取转换
groupingBy 详解
作用:把流中的元素按某个规则分组,装进 Map 里。key 是分组标识,value 是每组的汇总结果。
语法拆分:
▼java复制代码Collectors.groupingBy( 元素 -> 分组key, // Function<T, K>:每个元素属于哪一组 Collectors.xxx() // Collector:每组怎么统计 )
两种用法:
▼java复制代码// 用法1:只传分组规则 → 结果是 Map<K, List<T>> Map<String, List<Worker>> byJob = workers.stream() .collect(Collectors.groupingBy(w -> w.getJobType())); // 用法2:分组规则 + 统计方式 → 结果是 Map<K, 统计结果> Map<String, Long> countByJob = workers.stream() .collect(Collectors.groupingBy( w -> w.getJobType(), Collectors.counting() ));
常用统计方式:
| 统计方式 | 每组做什么 | 结果类型 |
|---|---|---|
Collectors.counting() | 数有几个 | Map<K, Long> |
Collectors.summingDouble(fn) | 求和 | Map<K, Double> |
Collectors.averagingDouble(fn) | 算平均 | Map<K, Double> |
Collectors.toList() | 收集成列表 | Map<K, List<T>> |
Collectors.maxBy(Comparator) | 找最大 | Map<K, Optional<T>> |
Collectors.minBy(Comparator) | 找最小 | Map<K, Optional<T>> |
完整示例:
▼java复制代码// 按工种分组,统计每类师傅的评分总和 Map<String, Double> sumByJob = workers.stream() .collect(Collectors.groupingBy( w -> w.getJobType(), Collectors.summingDouble(w -> w.getScore()) )); // 按客户类型分组,统计每类有几单 Map<String, Long> countByType = quotations.stream() .collect(Collectors.groupingBy( q -> q.getClient() instanceof Customer ? "业主" : "公司客户", Collectors.counting() )); // 复杂条件分组 Map<String, List<Product>> map = list.stream() .collect(Collectors.groupingBy(p -> { if (p.getPrice() > 1000) return "高价商品"; else return "低价商品"; }));
多字段分组(嵌套 groupingBy):
▼java复制代码Map<String, Map<String, List<Worker>>> nested = workers.stream() .collect(Collectors.groupingBy( w -> w.getJobType(), Collectors.groupingBy( w -> w.getScore() > 4.0 ? "高分" : "普通" ) )); // 结果:{布线工={高分=[赵孙钱], 普通=[...]}, 安装工={...}}
常见坑:
- 只传一个参数时,结果是
Map<K, List<T>>,不是计数! - groupingBy 的 key 可以为 null,业务尽量避免
- groupingBy 不保证顺序,需要顺序用
LinkedHashMap - collect 是终端操作,执行后不能继续调用 stream 中间方法
partitioningBy 详解
作用:特殊的分组,永远只有 true / false 两组,返回 Map<Boolean, List<元素>>。适合二分类。
和 groupingBy 对比:
| 方法 | Lambda 返回 | 分组数量 | 返回 Map 的 key |
|---|---|---|---|
groupingBy | 任意类型 | 多组(不限) | 自定义对象(String/Integer 等) |
partitioningBy | boolean | 固定 2 组 | true / false |
示例:
▼java复制代码// 价格大于1000为true,其余false Map<Boolean, List<Product>> partMap = list.stream() .collect(Collectors.partitioningBy(p -> p.getPrice() > 1000)); // 分区 + 统计数量 Map<Boolean, Long> countMap = list.stream() .collect(Collectors.partitioningBy( p -> p.getPrice() > 1000, Collectors.counting() )); // 结果遍历 countMap.forEach((isHigh, num) -> { System.out.println((isHigh ? "高价" : "低价") + ":" + num + "个"); });
Collectors 详解
Collectors 是 Stream 流水线的**"打包车间"**——Stream 负责加工数据,Collectors 负责把加工好的数据装进容器。
核心打包方式:
| 方法 | 打包结果 | 示例 |
|---|---|---|
Collectors.toList() | 装进 ArrayList | List<String> list = stream.collect(Collectors.toList()) |
Collectors.toSet() | 装进 HashSet(去重) | Set<String> set = stream.collect(Collectors.toSet()) |
Collectors.toMap(keyFn, valFn) | 装进 HashMap | Map<String, Double> map = stream.collect(Collectors.toMap(q -> q.getId(), q -> q.getAmount())) |
Collectors.groupingBy(...) | 按条件分组装 Map | 见上方 groupingBy 详解 |
Collectors.partitioningBy(...) | 真假二分区 | 见上方 partitioningBy 详解 |
Collectors.counting() | 计数 | .collect(Collectors.counting()) |
Collectors.summingDouble(fn) | 求和 | .collect(Collectors.summingDouble(q -> q.getAmount())) |
Collectors.averagingDouble(fn) | 平均值 | .collect(Collectors.averagingDouble(q -> q.getAmount())) |
Collectors.joining(",") | 用逗号拼接字符串 | stream.collect(Collectors.joining(",")) |
Collectors vs Collections 区分:
Collectors | Collections | |
|---|---|---|
| 给谁用 | Stream 的 collect() 方法 | 普通 Collection(List/Set/Map) |
| 什么时候用 | Stream 流水线结尾 | 直接操作已有集合 |
| 典型用法 | .collect(Collectors.toList()) | Collections.sort(list) |
| 本质 | 定义"怎么打包" | 定义"怎么操作" |
stream().filter().count() vs Collections.frequency() 对比
| 对比项 | stream().filter().count() | Collections.frequency() |
|---|---|---|
| 做什么 | 按任意条件统计满足的有几个 | 统计某个具体值出现了几次 |
| 参数 | Lambda 条件(> < instanceof 都行) | 一个精确匹配的对象 |
| 比较逻辑 | 你的条件表达式 | 只能用 equals() 精确匹配 |
| 返回类型 | long | int |
| 适用对象 | Stream 流 | 普通 Collection(List/Set) |
| 自定义对象 | ✅ 好用 | ❌ 基本不能用(比的是对象地址) |
场景1:按条件统计 → 用 Stream
▼java复制代码long count = quotations.stream() .filter(q -> q.calcOriginalTotalAmount() > 30000) .count();
场景2:精确匹配某个值出现几次 → 用 Collections.frequency
▼java复制代码List<String> names = Arrays.asList("赵孙钱", "钱多多", "赵孙钱", "李四海"); int times = Collections.frequency(names, "赵孙钱"); // 结果:2
Stream 常见坑汇总
-
Stream 中间操作不会修改原集合,只是读取。
▼java复制代码quotations.stream().sorted(...); // quotations 本身顺序不变 -
没有终结操作,中间操作不执行(惰性求值)。
▼java复制代码quotations.stream().filter(q -> { System.out.println("过滤"); return true; }); // ❌ 没有任何打印 -
Stream 只能消费一次。
▼java复制代码Stream<SmartHomeQuotation> s = quotations.stream(); s.forEach(...); // 第一次消费 s.forEach(...); // ❌ 报错:stream has already been operated upon -
collect 是终端操作,执行后不能继续调用 stream 中间方法。
▼java复制代码list.stream().collect(Collectors.toList()).stream().filter(...); // 要先 collect,再重新 stream() -
groupingBy / partitioningBy 的 key 可以为 null,业务尽量避免。
-
基础流
IntStream/LongStream/DoubleStream和包装类流Stream<Integer>不是同一个类型。Stream<Integer>没有sum()方法IntStream才有sum()、average()
-
flatMap 必须返回 Stream,不能直接返回集合。
▼java复制代码❌ .flatMap(o -> o.getProductList()) // 返回 List,编译错误 ✅ .flatMap(o -> o.getProductList().stream()) // 返回 Stream -
在 Stream 的 Lambda 中修改外部变量 → 编译错误。
▼java复制代码int sum = 0; list.stream().forEach(x -> sum += x); // ❌ 编译错误 // 正确做法:用 mapToDouble + sum()
六、Lambda 表达式
基础介绍
Lambda 是 JDK 8 引入的匿名函数,用于简化函数式接口(只有一个抽象方法的接口)的实现。
基础语法
▼java复制代码// 无参数 () -> System.out.println("Hello") // 一个参数,类型可省略 x -> x * 2 // 多个参数 (a, b) -> a + b // 多条语句用 {} 包裹 (x, y) -> { int sum = x + y; return sum; }
常用函数式接口
| 接口 | 方法 | Lambda 示例 | Stream 中对应 |
|---|---|---|---|
Predicate<T> | test(T) | x -> x > 0 | filter |
Function<T,R> | apply(T) | x -> x.toString() | map |
Consumer<T> | accept(T) | x -> System.out.println(x) | forEach |
Supplier<T> | get() | () -> new Random().nextInt() | 创建 Stream |
ToDoubleFunction<T> | applyAsDouble(T) | x -> x.getPrice() | mapToDouble |
方法引用 ::
| 类型 | 写法 | 等价 Lambda | 使用场景 |
|---|---|---|---|
| 对象方法引用 | 对象::方法 | x -> 对象.方法(x) | 固定对象调方法 |
| 类方法引用 | 类名::方法 | x -> x.方法() | Stream 元素调自身方法 |
| 静态方法引用 | 类名::静态方法 | (a,b) -> 类名.静态方法(a,b) | 工具类方法 |
| 构造器引用 | 类名::new | () -> new 类名() | 创建对象 |
▼java复制代码// 类名::实例方法 → Stream 中常用 workers.stream().map(Worker::getName) // 等价于:workers.stream().map(w -> w.getName())
注意点
- Lambda 只能用于函数式接口
- Lambda 中不能修改外部局部变量(必须是 effectively final)
七、Comparator 排序
工作原理
compare(a, b) 返回 int,排序器根据这个 int 决定谁在前谁在后。
| compare(a, b) 返回值 | 含义 | 结果 |
|---|---|---|
| 负数 | a 排在 b 前面 | a 赢了 |
| 正数 | a 排在 b 后面 | b 赢了 |
| 0 | a 和 b 相等 | 顺序不变 |
口诀
a - b→ 负数 → a 在前 → 升序(从小到大)b - a→ 正数 → b 在前 → 降序(从大到小)
经典代码
▼java复制代码// 按工龄升序:正常减 Collections.sort(workers, (a, b) -> a.getWorkYear() - b.getWorkYear()); // 按评分降序:反过来比(double 用 Double.compare) Collections.sort(workers, (a, b) -> Double.compare(b.getScore(), a.getScore())); // 按姓名拼音升序:String 自带 compareTo Collections.sort(workers, (a, b) -> a.getName().compareTo(b.getName())); // 多条件排序:先按工种,再按工龄降序 Collections.sort(workers, (a, b) -> { int jobCompare = a.getJobType().compareTo(b.getJobType()); if (jobCompare != 0) return jobCompare; // 工种不同,按工种排 return b.getWorkYear() - a.getWorkYear(); // 工种相同,按工龄降序 }); // Comparator 链式写法(隐藏 if 逻辑) workers.sort(Comparator .comparing(Worker::getJobType) .thenComparing((a, b) -> b.getWorkYear() - a.getWorkYear()));
附录:集合框架速查
选择哪个集合?
| 需求 | 选择 | 理由 |
|---|---|---|
| 有序列表,按顺序遍历 | ArrayList | 动态数组,查询快 |
| 频繁在头部/尾部增删 | LinkedList | 双向链表,首尾操作快 |
| 键值对,按 key 快速查找 | HashMap | 哈希表,O(1) 查找 |
| 键值对,需要保持存入顺序 | LinkedHashMap | 链表维护插入顺序 |
| 去重,判断是否存在 | HashSet | 基于 HashMap 实现 |
三级流水线
▼text复制代码数据库(查) → Stream(加工) → Collectors(打包) ↓ ↓ ↓ 原始数据 filter/map List/Map/数字
常见遍历方式对比
▼java复制代码// ArrayList 遍历 for (int i = 0; i < list.size(); i++) // 需要序号 for (Worker w : workers) // 纯遍历 workers.stream().filter(...).forEach(...) // 过滤统计 // HashMap 遍历 for (Map.Entry<K,V> entry : map.entrySet()) // key-value 都要 for (K key : map.keySet()) // 只要 key for (V value : map.values()) // 只要 value map.forEach((k, v) -> ...) // Lambda
