Java集合Map与Stream流:Map实现类特点、遍历方式、Stream流操作及Collections工具类方法
Java集合Map与Stream流:包含Map实现类特点、遍历方式、Stream流操作及Collections工具类方法
- 1. 认识Map以及实现类的特点
- 1.1 Map的核心概念
- 1.2 三大实现类的特点对比
- 1.3 代码案例:三种Map的基本使用对比
- 2. Map提供的常用方法
- 3. 三种遍历方式
- 3.1 方式一:键找值(通过`keySet()`遍历键,再获取值)
- 3.2 方式二:键值对(通过`entrySet()`遍历,推荐)
- 3.3 方式三:Lambda表达式(JDK8+,简洁)
- 4. HashMap、LinkedHashMap、TreeMap的常见应用场景案例
- 4.1 HashMap:无需顺序的键值对存储(最常用)
- 4.2 LinkedHashMap:需要保持插入顺序的场景
- 4.3 TreeMap:需要排序的场景
- 5. 认识Stream流
- 5.1 什么是Stream流?
- 5.2 作用与优势
- 5.3 处理数据的步骤
- 5.4 数据处理方式与结果应用
- 6. 获取Stream流的几种方式
- 6.1 从集合获取流
- 6.2 从数组获取流
- 6.3 通过`Stream.of()`方法获取流
- 7. Stream常用中间方法及代码案例
- 7.1 `filter(Predicate<T> predicate)`:过滤元素
- 7.2 `sorted()`/`sorted(Comparator<T> comparator)`:排序
- 7.3 `distinct()`:去重
- 7.4 `map(Function<T, R> mapper)`:转换元素类型
- 7.5 `limit(long maxSize)`:限制元素数量
- 7.6 `skip(long n)`:跳过元素
- 8. Stream常用终结方法及代码案例
- 8.1 `forEach(Consumer<T> action)`:遍历元素
- 8.2 `count()`:统计元素数量
- 8.3 `collect(Collector<T, A, R> collector)`:收集为集合
- 8.4 `max(Comparator<T> comparator)`/`min(Comparator<T> comparator)`:最大/小值
- 8.5 `anyMatch(Predicate<T> predicate)`:判断是否有元素满足条件
- 9. Collections工具类操作集合的常用方法
- 9.1 排序与反转
- 9.2 查找与替换
- 9.3 集合拷贝与同步化
- 9.4 可变参数方法(重点强调)
- 9.5 其他常用方法
1. 认识Map以及实现类的特点
1.1 Map的核心概念
Map是Java集合框架中用于存储键值对(Key-Value) 的接口,与Collection(单列集合)不同,Map是双列集合,其核心特点是:
- 键(Key)唯一:不允许重复,若添加重复键,新值会覆盖旧值
- 值(Value)可重复:不同键可对应相同值
- 键值对映射:通过键快速查找值,类似于现实中的“字典”(键=单词,值=释义)
1.2 三大实现类的特点对比
实现类 | 底层结构 | 有序性 | 排序性 | 线程安全 | 性能(增删查) | 核心特点 |
---|---|---|---|---|---|---|
HashMap | 哈希表(数组+链表+红黑树) | 无序(键值对存储顺序与插入顺序无关) | 无(需手动排序) | 否 | 高(O(1)平均) | JDK8后当链表长度>8时转为红黑树,兼顾数组查询快和链表增删快的优势 |
LinkedHashMap | 哈希表+双向链表 | 有序(保持插入顺序) | 无 | 否 | 略低于HashMap | 通过双向链表记录插入顺序,适合需要“按添加顺序访问”的场景(如历史记录) |
TreeMap | 红黑树(自平衡二叉树) | 有序(按键自然排序或定制排序) | 有(键需可比较) | 否 | 中(O(log n)) | 键必须实现Comparable 接口或传入Comparator ,适合需要“排序键值对”的场景 |
1.3 代码案例:三种Map的基本使用对比
import java.util.*;public class MapDemo {public static void main(String[] args) {// 1. HashMap:无序Map<String, Integer> hashMap = new HashMap<>();hashMap.put("苹果", 5);hashMap.put("香蕉", 3);hashMap.put("橙子", 4);System.out.println("HashMap(无序):" + hashMap); // 输出顺序可能为 {香蕉=3, 苹果=5, 橙子=4}// 2. LinkedHashMap:保持插入顺序Map<String, Integer> linkedHashMap = new LinkedHashMap<>();linkedHashMap.put("苹果", 5);linkedHashMap.put("香蕉", 3);linkedHashMap.put("橙子", 4);System.out.println("LinkedHashMap(插入顺序):" + linkedHashMap); // 输出 {苹果=5, 香蕉=3, 橙子=4}// 3. TreeMap:按键自然排序(String默认按字典序)Map<String, Integer> treeMap = new TreeMap<>();treeMap.put("苹果", 5);treeMap.put("香蕉", 3);treeMap.put("橙子", 4);System.out.println("TreeMap(自然排序):" + treeMap); // 输出 {苹果=5, 橙子=4, 香蕉=3}(字典序:苹<橙<香)}
}
2. Map提供的常用方法
Map接口定义了操作键值对的核心方法,以下是最常用的API及案例(以HashMap为例):
方法名 | 作用 | 代码案例(基于Map<String, Integer> map = new HashMap<>() ) |
---|---|---|
put(K key, V value) | 添加/修改键值对(键存在则覆盖值) | map.put("苹果", 5); (添加);map.put("苹果", 6); (修改为6) |
get(Object key) | 根据键获取值(键不存在返回null ) | Integer count = map.get("苹果"); (返回5) |
remove(Object key) | 根据键删除键值对,返回被删除的值 | Integer removed = map.remove("苹果"); (返回5,map中移除"苹果") |
containsKey(Object key) | 判断是否包含指定键 | boolean hasApple = map.containsKey("苹果"); (返回true) |
containsValue(Object value) | 判断是否包含指定值 | boolean has5 = map.containsValue(5); (返回true) |
size() | 返回键值对数量 | int size = map.size(); (返回当前键值对总数) |
isEmpty() | 判断是否为空(键值对数量为0) | boolean empty = map.isEmpty(); (返回false,因已添加数据) |
clear() | 清空所有键值对 | map.clear(); (map变为空) |
keySet() | 返回所有键的集合(Set<K> ) | Set<String> keys = map.keySet(); (获取所有水果名称) |
values() | 返回所有值的集合(Collection<V> ) | Collection<Integer> values = map.values(); (获取所有数量) |
entrySet() | 返回键值对集合(Set<Map.Entry<K,V>> ) | Set<Map.Entry<String, Integer>> entries = map.entrySet(); |
3. 三种遍历方式
3.1 方式一:键找值(通过keySet()
遍历键,再获取值)
原理:先获取所有键的集合(keySet()
),再遍历键集合,通过get(key)
获取对应值。
适用场景:只需遍历键或通过键获取值的场景。
Map<String, Integer> fruitMap = new HashMap<>();
fruitMap.put("苹果", 5);
fruitMap.put("香蕉", 3);
fruitMap.put("橙子", 4);// 1. 获取所有键的集合
Set<String> keys = fruitMap.keySet();
// 2. 遍历键集合(增强for循环)
for (String key : keys) {// 3. 通过键获取值Integer value = fruitMap.get(key);System.out.println(key + ":" + value); // 输出:苹果:5,香蕉:3,橙子:4(顺序不确定)
}
3.2 方式二:键值对(通过entrySet()
遍历,推荐)
原理:entrySet()
将每个键值对封装为Map.Entry<K,V>
对象(Entry是Map的内部接口,包含getKey()
和getValue()
方法),直接遍历Entry对象即可同时获取键和值,性能优于键找值(无需多次调用get(key)
)。
// 1. 获取键值对集合(每个元素是Entry对象)
Set<Map.Entry<String, Integer>> entries = fruitMap.entrySet();
// 2. 遍历Entry集合(迭代器方式)
Iterator<Map.Entry<String, Integer>> iterator = entries.iterator();
while (iterator.hasNext()) {Map.Entry<String, Integer> entry = iterator.next();String key = entry.getKey(); // 获取键Integer value = entry.getValue(); // 获取值System.out.println(key + ":" + value);
}
3.3 方式三:Lambda表达式(JDK8+,简洁)
原理:通过forEach(BiConsumer<? super K, ? super V> action)
方法,直接传入Lambda表达式遍历键值对,最简洁。
// Lambda表达式遍历:(key, value) -> 处理逻辑
fruitMap.forEach((key, value) -> {System.out.println(key + ":" + value);
});
4. HashMap、LinkedHashMap、TreeMap的常见应用场景案例
4.1 HashMap:无需顺序的键值对存储(最常用)
场景:存储用户信息(键=用户ID,值=用户对象),无需关心顺序,追求查询效率。
// 存储学生信息:键=学号(唯一),值=学生对象
Map<String, Student> studentMap = new HashMap<>();
studentMap.put("2023001", new Student("张三", 18));
studentMap.put("2023002", new Student("李四", 19));
// 快速查询学号为2023001的学生
Student zhangsan = studentMap.get("2023001");
4.2 LinkedHashMap:需要保持插入顺序的场景
场景:记录用户操作历史(按操作顺序展示),如浏览器的“历史记录”(先访问的网站在前)。
// 存储浏览历史:键=URL,值=访问时间,保持插入顺序
Map<String, String> history = new LinkedHashMap<>();
history.put("https://www.baidu.com", "2023-10-01 08:00");
history.put("https://www.google.com", "2023-10-01 09:00");
history.put("https://www.github.com", "2023-10-01 10:00");
// 遍历输出时,顺序与插入顺序一致(百度→谷歌→GitHub)
history.forEach((url, time) -> System.out.println(time + ":" + url));
4.3 TreeMap:需要排序的场景
场景:商品价格排行榜(按价格升序/降序排列),或字典(按字母顺序排列单词)。
// 存储商品价格:键=商品名,值=价格,按价格升序排序(默认自然排序,String按字典序,Integer按数字序)
Map<String, Double> productPrices = new TreeMap<>();
productPrices.put("笔记本", 4999.9);
productPrices.put("手机", 2999.9);
productPrices.put("平板", 1999.9);
// 遍历输出时,键按字典序排列:平板→手机→笔记本(价格1999.9→2999.9→4999.9)
productPrices.forEach((product, price) -> System.out.println(product + ":" + price + "元"));// 若需按价格降序,可传入自定义比较器:
Map<String, Double> sortedByPriceDesc = new TreeMap<>((a, b) -> productPrices.get(b).compareTo(productPrices.get(a)) // 降序:后减前
);
sortedByPriceDesc.putAll(productPrices); // 此时顺序为:笔记本→手机→平板
5. 认识Stream流
5.1 什么是Stream流?
Stream流是Java 8引入的处理集合数据的高级工具,它不是数据结构,而是数据处理管道,可以对集合、数组等数据源进行高效的聚合操作(如过滤、排序、统计等)。
5.2 作用与优势
- 简化代码:用链式调用替代传统的for循环,代码更简洁易读
- 函数式编程:支持Lambda表达式,专注“做什么”而非“怎么做”
- 惰性执行:中间操作仅记录操作逻辑,不立即执行,直到调用终结操作才触发处理
- 并行处理:可轻松切换为并行流(
parallelStream()
),利用多核CPU提高处理效率
5.3 处理数据的步骤
Stream流处理分为3步:
- 获取流:从数据源(集合、数组等)创建Stream流
- 中间操作:对数据进行处理(过滤、排序、转换等),返回新的Stream流(可链式调用多个中间操作)
- 终结操作:触发流处理,返回结果(如遍历、统计、收集为集合),流一旦终结则不可再使用
5.4 数据处理方式与结果应用
-
数据处理方式(中间操作):
- 过滤(
filter
):保留满足条件的元素 - 排序(
sorted
):对元素排序(自然排序或定制排序) - 去重(
distinct
):去除重复元素(依赖equals()
和hashCode()
) - 转换(
map
):将元素转换为另一种类型(如将字符串转为长度) - 限制(
limit(n)
):保留前n个元素 - 跳过(
skip(n)
):跳过前n个元素
- 过滤(
-
结果应用方式(终结操作):
- 遍历(
forEach
):逐个处理元素(无返回值) - 统计(
count
):返回元素数量;max
/min
:返回最大/小值 - 收集(
collect
):将结果收集为集合(如List
、Set
、Map
) - 匹配(
anyMatch
/allMatch
/noneMatch
):判断是否满足条件 - 查找(
findFirst
/findAny
):返回第一个/任意元素
- 遍历(
6. 获取Stream流的几种方式
6.1 从集合获取流
- List/Set集合:直接调用
stream()
或parallelStream()
(并行流) - Map集合:需先获取键集、值集或键值对集,再调用
stream()
// List集合
List<String> list = Arrays.asList("苹果", "香蕉", "橙子");
Stream<String> listStream = list.stream();// Map集合(键流、值流、键值对流)
Map<String, Integer> map = new HashMap<>();
map.put("苹果", 5);
map.put("香蕉", 3);
Stream<String> keyStream = map.keySet().stream(); // 键流(水果名)
Stream<Integer> valueStream = map.values().stream(); // 值流(数量)
Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); // 键值对流
6.2 从数组获取流
通过Arrays.stream(数组)
获取流:
String[] fruits = {"苹果", "香蕉", "橙子"};
Stream<String> arrayStream = Arrays.stream(fruits);int[] numbers = {1, 2, 3, 4};
IntStream intStream = Arrays.stream(numbers); // 基本类型数组对应IntStream/LongStream等
6.3 通过Stream.of()
方法获取流
Stream.of(T... values)
可直接传入多个参数或数组创建流:
// 多个参数
Stream<String> singleParamStream = Stream.of("苹果"); // 单一参数
Stream<String> multiParamStream = Stream.of("苹果", "香蕉", "橙子"); // 多个参数// 数组(注意:传入数组时,若为引用类型数组,流元素为数组本身;需用Arrays.stream()获取数组元素流)
String[] arr = {"苹果", "香蕉"};
Stream<String[]> arrayAsElementStream = Stream.of(arr); // 流元素是String[]数组
Stream<String> arrayElementsStream = Arrays.stream(arr); // 流元素是数组中的字符串(正确方式)
7. Stream常用中间方法及代码案例
中间操作返回新的Stream流,可链式调用,常用方法及案例如下(以List<Integer> numbers = Arrays.asList(3, 1, 2, 3, 4, 5)
为例):
7.1 filter(Predicate<T> predicate)
:过滤元素
保留满足条件的元素(Predicate
是函数式接口,接收T返回boolean)。
// 过滤出偶数(保留2、4)
Stream<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0); // n为流中的每个元素,返回true则保留
7.2 sorted()
/sorted(Comparator<T> comparator)
:排序
sorted()
:自然排序(元素需实现Comparable
接口)sorted(comparator)
:定制排序(传入比较器)
// 自然排序(1, 2, 3, 3, 4, 5)
Stream<Integer> sortedNatural = numbers.stream().sorted();// 降序排序(5, 4, 3, 3, 2, 1)
Stream<Integer> sortedDesc = numbers.stream().sorted((a, b) -> b - a); // 定制比较器:后减前为降序
7.3 distinct()
:去重
去除重复元素(依赖元素的equals()
和hashCode()
方法)。
// 去重后:3, 1, 2, 4, 5(原集合中的两个3保留一个)
Stream<Integer> distinctNumbers = numbers.stream().distinct();
7.4 map(Function<T, R> mapper)
:转换元素类型
将元素转换为另一种类型(Function
接口:接收T,返回R)。
// 将整数转换为字符串("3", "1", "2", "3", "4", "5")
Stream<String> numberStrings = numbers.stream().map(n -> "数字:" + n); // 每个元素转为"数字:n"格式
7.5 limit(long maxSize)
:限制元素数量
保留前maxSize
个元素。
// 保留前3个元素:3, 1, 2
Stream<Integer> limited = numbers.stream().limit(3);
7.6 skip(long n)
:跳过元素
跳过前n
个元素,保留剩余元素。
// 跳过前2个元素:2, 3, 4, 5
Stream<Integer> skipped = numbers.stream().skip(2);
8. Stream常用终结方法及代码案例
终结操作触发流处理并返回结果,常用方法及案例如下(数据源同上:List<Integer> numbers = Arrays.asList(3, 1, 2, 3, 4, 5)
):
8.1 forEach(Consumer<T> action)
:遍历元素
逐个处理元素(Consumer
接口:接收T,无返回值)。
// 遍历输出每个元素
numbers.stream().forEach(n -> System.out.print(n + " ")); // 输出:3 1 2 3 4 5
8.2 count()
:统计元素数量
返回流中元素的个数(long
类型)。
// 统计偶数个数(2、4 → 共2个)
long evenCount = numbers.stream().filter(n -> n % 2 == 0).count();
System.out.println("偶数个数:" + evenCount); // 输出:2
8.3 collect(Collector<T, A, R> collector)
:收集为集合
将流元素收集为List
、Set
、Map
等,需配合Collectors
工具类。
import java.util.stream.Collectors;// 收集为List(去重后的偶数:2, 4)
List<Integer> evenList = numbers.stream().filter(n -> n % 2 == 0).distinct().collect(Collectors.toList());// 收集为Set(自动去重:3, 1, 2, 4, 5)
Set<Integer> numberSet = numbers.stream().collect(Collectors.toSet());
8.4 max(Comparator<T> comparator)
/min(Comparator<T> comparator)
:最大/小值
返回流中最大或最小的元素(Optional<T>
类型,避免空指针)。
// 求最大值(5)
Optional<Integer> maxNum = numbers.stream().max(Integer::compare); // 自然排序比较器(等价于(a,b)->a.compareTo(b))
System.out.println("最大值:" + maxNum.get()); // 输出:5// 求最小值(1)
Optional<Integer> minNum = numbers.stream().min(Integer::compare);
System.out.println("最小值:" + minNum.get()); // 输出:1
8.5 anyMatch(Predicate<T> predicate)
:判断是否有元素满足条件
返回boolean
,只要有一个元素满足条件则返回true。
// 判断是否存在大于4的元素(5>4 → true)
boolean hasGreaterThan4 = numbers.stream().anyMatch(n -> n > 4);
System.out.println("是否存在大于4的元素:" + hasGreaterThan4); // 输出:true
9. Collections工具类操作集合的常用方法
java.util.Collections
是操作集合的工具类,提供了大量静态方法,以下是常用方法及案例:
9.1 排序与反转
sort(List<T> list)
:对List进行自然排序(元素需实现Comparable
)sort(List<T> list, Comparator<T> c)
:定制排序reverse(List<?> list)
:反转List中元素的顺序
List<Integer> nums = new ArrayList<>(Arrays.asList(3, 1, 2));
Collections.sort(nums); // 自然排序:[1, 2, 3]
Collections.reverse(nums); // 反转:[3, 2, 1]
Collections.sort(nums, (a, b) -> b - a); // 定制降序:[3, 2, 1](与反转结果一致)
9.2 查找与替换
max(Collection<? extends T> coll)
/min(Collection<? extends T> coll)
:返回最大/小元素fill(List<? super T> list, T obj)
:用指定元素填充List(替换所有元素)replaceAll(List<T> list, T oldVal, T newVal)
:替换List中所有旧值为新值
List<String> fruits = new ArrayList<>(Arrays.asList("苹果", "香蕉", "苹果"));
String maxFruit = Collections.max(fruits); // 自然排序最大值(按字典序:香蕉)
Collections.fill(fruits, "橙子"); // 填充后:[橙子, 橙子, 橙子]
Collections.replaceAll(fruits, "橙子", "葡萄"); // 替换后:[葡萄, 葡萄, 葡萄]
9.3 集合拷贝与同步化
copy(List<? super T> dest, List<? extends T> src)
:将src中的元素拷贝到dest(需保证dest.size() ≥ src.size())synchronizedList(List<T> list)
:将非线程安全的List转为线程安全的List(同理有synchronizedSet
、synchronizedMap
)
List<Integer> src = Arrays.asList(1, 2, 3);
List<Integer> dest = new ArrayList<>(Arrays.asList(0, 0, 0, 0)); // dest容量≥src(3个元素)
Collections.copy(dest, src); // dest变为:[1, 2, 3, 0](前3个元素被src覆盖)// 同步化集合(多线程环境下使用)
List<String> syncFruits = Collections.synchronizedList(new ArrayList<>());
syncFruits.add("苹果"); // 线程安全的添加操作
9.4 可变参数方法(重点强调)
Collections
的addAll
、nCopies
等方法支持可变参数(T... elements
),可变参数本质是数组的简化写法,允许传入任意数量的参数(0个或多个)。
addAll(Collection<? super T> c, T... elements)
:向集合中添加多个元素nCopies(int n, T o)
:返回包含n个相同元素的不可变List
List<String> list = new ArrayList<>();
// 可变参数:传入多个元素(苹果、香蕉、橙子),等价于传入数组new String[]{"苹果", "香蕉", "橙子"}
Collections.addAll(list, "苹果", "香蕉", "橙子"); // list变为:[苹果, 香蕉, 橙子]// nCopies:创建包含3个"重复"的不可变List
List<String> repeated = Collections.nCopies(3, "重复"); // [重复, 重复, 重复]
可变参数注意事项:
- 一个方法只能有一个可变参数,且必须是最后一个参数
- 调用时可传入数组,也可直接传入多个参数(编译器自动转为数组)
9.5 其他常用方法
shuffle(List<?> list)
:随机打乱List元素顺序(如洗牌)frequency(Collection<?> c, Object o)
:统计元素在集合中出现的次数
List<Integer> cards = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
Collections.shuffle(cards); // 随机打乱顺序(如[3, 1, 5, 2, 4])
int count = Collections.frequency(cards, 3); // 统计3出现的次数(1次)