
简介:
在Java8的诸多新特性中,Stream流绝对是提升集合操作效率的"利器"。它以声明式编程风格简化了集合遍历与数据处理逻辑,同时支持并行处理,让复杂的数据操作代码更简洁、更易维护。本文将从核心概念入手,通过大量实战案例拆解Stream流的使用方式,梳理关键注意点,并结合高频面试题深化理解,全程贯穿实战思维。
Stream流并非集合本身,而是对集合或数组等数据源进行操作的序列。它就像一条"流水线",数据从数据源进入流水线后,经过一系列中间操作(过滤、转换、排序等)的处理,最终通过终止操作输出结果。Stream流具有以下核心特征:
parallelStream()即可实现并行处理传统集合操作需通过循环(for、foreach)手动控制遍历过程,代码冗余且可读性差;而Stream流采用声明式风格,只需关注"做什么",无需关注"怎么遍历"。
<font style="background-color:rgb(255,245,235);">需求</font><font style="background-color:rgb(255,245,235);">:从List<Person>中筛选出年龄大于25岁的男性,提取姓名并按年龄升序排序</font>
// 定义实体类
class Person {
private String name;
private int age;
private String gender;
// 构造器、getter、setter省略
}
public class TraditionalDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女"),
new Person("赵六", 30, "男")
);
// 传统操作
List<String> result = new ArrayList<>();
for (Person person : personList) {
// 筛选年龄>25且男性
if (person.getAge() > 25 && "男".equals(person.getGender())) {
result.add(person.getName());
}
}
// 排序
Collections.sort(result, new Comparator<String>() {
@Override
public int compare(String o1, String o2) {
// 需通过姓名反查年龄,逻辑繁琐
return getAgeByName(personList, o1) - getAgeByName(personList, o2);
}
});
System.out.println(result);
}
// 辅助方法:通过姓名查年龄
private static int getAgeByName(List<Person> list, String name) {
for (Person p : list) {
if (name.equals(p.getName())) {
return p.getAge();
}
}
return 0;
}
}public class StreamDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女"),
new Person("赵六", 30, "男")
);
List<String> result = personList.stream()
// 筛选年龄>25且男性
.filter(p -> p.getAge() > 25 && "男".equals(p.getGender()))
// 按年龄升序排序
.sorted(Comparator.comparingInt(Person::getAge))
// 提取姓名
.map(Person::getName)
// 收集结果到List
.collect(Collectors.toList());
System.out.println(result); // 输出:[李四, 赵六]
}
}对比可见,Stream流代码更简洁,逻辑链清晰,无需关注遍历和排序的底层实现。
Stream流的操作分为三个阶段:创建流 → 中间操作 → 终止操作,流程如下:
暂时无法在豆包文档外展示此内容
常见的流创建方式有5种,覆盖不同数据源场景:
public class StreamCreateDemo {
public static void main(String[] args) {
// 1. 从集合创建(最常用)
List<String> list = Arrays.asList("a", "b", "c");
Stream<String> listStream = list.stream(); // 串行流
Stream<String> parallelListStream = list.parallelStream(); // 并行流
// 2. 从数组创建
String[] arr = {"x", "y", "z"};
Stream<String> arrStream = Arrays.stream(arr);
// 3. 从单个或多个值创建
Stream<String> valueStream = Stream.of("m", "n");
// 4. 创建空流(避免空指针)
Stream<String> emptyStream = Stream.empty();
// 5. 无限流(需配合limit终止操作)
Stream<Integer> infiniteStream = Stream.iterate(0, n -> n + 2); // 0,2,4,6...
infiniteStream.limit(5).forEach(System.out::println); // 输出前5个:0 2 4 6 8
}
}中间操作用于对数据进行处理,支持链式调用,常见操作可分为过滤、映射、排序、限制/跳过、去重五大类,实战案例如下:
根据条件筛选数据,Predicate是函数式接口,接收T类型参数,返回boolean。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:筛选List<Integer>中大于10且为偶数的元素</font>
public class FilterDemo {
public static void main(String[] args) {
List<Integer> numList = Arrays.asList(5, 12, 8, 20, 15, 18);
numList.stream()
.filter(num -> num > 10 && num % 2 == 0)
.forEach(System.out::println); // 输出:12 20 18
}
}map:将T类型数据转换为R类型(一对一映射);flatMap:将T类型数据转换为Stream<R>,再合并为一个Stream(一对多映射,解决嵌套集合问题)。
<font style="background-color:rgb(255,245,235);">案例1(map)</font><font style="background-color:rgb(255,245,235);">:将List<String>中所有元素转为大写;</font><font style="background-color:rgb(255,245,235);">案例2(flatMap)</font><font style="background-color:rgb(255,245,235);">:将List<List<String>>拆分为单个String的Stream</font>
public class MapDemo {
public static void main(String[] args) {
// 案例1:map一对一映射
List<String> strList = Arrays.asList("apple", "banana", "cherry");
strList.stream()
.map(String::toUpperCase)
.forEach(System.out::println); // 输出:APPLE BANANA CHERRY
// 案例2:flatMap一对多映射(拆分嵌套集合)
List<List<String>> nestedList = Arrays.asList(
Arrays.asList("a", "b"),
Arrays.asList("c", "d"),
Arrays.asList("e", "f")
);
nestedList.stream()
.flatMap(Collection::stream) // 将每个子List转为Stream并合并
.forEach(System.out::println); // 输出:a b c d e f
}
}sorted():默认按自然顺序排序(需元素实现Comparable接口);sorted(Comparator):自定义排序规则。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:对Person列表先按年龄降序排序,年龄相同按姓名升序排序</font>
public class SortedDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女"),
new Person("赵六", 28, "男")
);
personList.stream()
// 自定义排序:年龄降序,姓名升序
.sorted(Comparator.comparingInt(Person::getAge).reversed()
.thenComparing(Person::getName))
.forEach(p -> System.out.println(p.getName() + ":" + p.getAge()));
// 输出:李四:28 赵六:28 王五:26 张三:23
}
}limit:取前N个元素;skip:跳过前N个元素,两者常配合使用实现分页。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:实现List<Integer>的分页,每页2条数据,取第2页(即第3、4个元素)</font>
public class LimitSkipDemo {
public static void main(String[] args) {
List<Integer> numList = Arrays.asList(1, 2, 3, 4, 5, 6);
int pageSize = 2; // 每页条数
int pageNum = 2; // 第2页
numList.stream()
.skip((pageNum - 1) * pageSize) // 跳过前2条(第1页)
.limit(pageSize) // 取2条(第2页)
.forEach(System.out::println); // 输出:3 4
}
}根据元素的equals()方法去重,若为自定义对象,需重写equals()和hashCode()方法。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:对List<Integer>去重,对自定义Person列表按姓名去重</font>
public class DistinctDemo {
public static void main(String[] args) {
// 案例1:基本类型去重
List<Integer> numList = Arrays.asList(1, 2, 2, 3, 3, 3);
numList.stream()
.distinct()
.forEach(System.out::println); // 输出:1 2 3
// 案例2:自定义对象去重(需重写Person的equals和hashCode)
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("张三", 25, "男"), // 姓名相同,需去重
new Person("李四", 28, "男")
);
personList.stream()
.distinct()
.forEach(System.out::println); // 输出:张三(23)、李四(28)
}
}终止操作触发流的执行,返回非Stream类型结果,常见操作分为收集、遍历、统计、匹配四大类:
将流处理结果收集为集合、数组或自定义对象,Collectors工具类提供了大量默认实现。
<font style="background-color:rgb(255,245,235);">常见场景</font><font style="background-color:rgb(255,245,235);">:收集为List/Set/Map、分组收集、聚合统计、拼接字符串</font>
public class CollectDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女"),
new Person("赵六", 30, "男"),
new Person("孙七", 27, "女")
);
// 1. 收集为List
List<String> nameList = personList.stream()
.map(Person::getName)
.collect(Collectors.toList());
// 2. 收集为Set(自动去重)
Set<Integer> ageSet = personList.stream()
.map(Person::getAge)
.collect(Collectors.toSet());
// 3. 收集为Map(姓名为key,年龄为value,需确保key唯一)
Map<String, Integer> nameAgeMap = personList.stream()
.collect(Collectors.toMap(
Person::getName,
Person::getAge,
(oldValue, newValue) -> oldValue // 解决key冲突:保留旧值
));
// 4. 按性别分组(key为性别,value为该性别下的Person列表)
Map<String, List<Person>> genderGroup = personList.stream()
.collect(Collectors.groupingBy(Person::getGender));
// 5. 分组后统计数量(key为性别,value为人数)
Map<String, Long> genderCount = personList.stream()
.collect(Collectors.groupingBy(
Person::getGender,
Collectors.counting() // 聚合函数
));
// 6. 拼接字符串(用逗号分隔姓名)
String nameJoin = personList.stream()
.map(Person::getName)
.collect(Collectors.joining(","));
System.out.println(nameJoin); // 输出:张三,李四,王五,赵六,孙七
}
}遍历流中的元素,Consumer是函数式接口,接收T类型参数,无返回值。
public class ForEachDemo {
public static void main(String[] args) {
List<String> strList = Arrays.asList("a", "b", "c");
// 遍历并打印
strList.stream().forEach(System.out::println);
// 并行遍历(注意:并行遍历顺序不保证)
strList.parallelStream().forEach(str ->
System.out.println(Thread.currentThread().getName() + ":" + str)
);
}
}针对数值型流(IntStream、LongStream、DoubleStream)的统计操作,可通过mapToInt()等方法转换为数值型流。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:统计Person列表的年龄总数、最大值、最小值、平均值</font>
public class StatisticDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女")
);
// 转换为IntStream(年龄为int类型)
IntStream ageStream = personList.stream()
.mapToInt(Person::getAge);
// 统计总数
long count = ageStream.count();
// 注意:流已被消费,需重新创建
int maxAge = personList.stream().mapToInt(Person::getAge).max().getAsInt();
int minAge = personList.stream().mapToInt(Person::getAge).min().getAsInt();
double avgAge = personList.stream().mapToInt(Person::getAge).average().getAsDouble();
System.out.println("总数:" + count); // 3
System.out.println("最大年龄:" + maxAge); // 28
System.out.println("最小年龄:" + minAge); // 23
System.out.println("平均年龄:" + avgAge); // 25.666...
}
}用于判断流中元素是否满足指定条件,返回boolean值,且支持短路求值(满足条件后立即停止遍历)。
<font style="background-color:rgb(255,245,235);">案例</font><font style="background-color:rgb(255,245,235);">:判断Person列表中是否有女性、是否所有年龄都大于20、是否没有年龄大于30的人</font>
public class MatchDemo {
public static void main(String[] args) {
List<Person> personList = Arrays.asList(
new Person("张三", 23, "男"),
new Person("李四", 28, "男"),
new Person("王五", 26, "女")
);
// 1. anyMatch:是否存在至少一个满足条件的元素(有女性?)
boolean hasFemale = personList.stream()
.anyMatch(p -> "女".equals(p.getGender()));
System.out.println(hasFemale); // true
// 2. allMatch:是否所有元素都满足条件(所有年龄>20?)
boolean allAgeGt20 = personList.stream()
.allMatch(p -> p.getAge() > 20);
System.out.println(allAgeGt20); // true
// 3. noneMatch:是否所有元素都不满足条件(没有年龄>30的?)
boolean noneAgeGt30 = personList.stream()
.noneMatch(p -> p.getAge() > 30);
System.out.println(noneAgeGt30); // true
}
}结合实际业务场景,实现"电商订单数据处理":从订单列表中筛选出2024年的有效订单,按用户ID分组,计算每个用户的订单总金额,并按总金额降序排序,最终只保留金额前3的用户。
// 订单实体类
class Order {
private String orderId; // 订单ID
private String userId; // 用户ID
private BigDecimal amount; // 订单金额
private LocalDate createTime; // 创建时间
private boolean valid; // 是否有效
// 构造器、getter、setter省略
}
public class OrderProcessDemo {
public static void main(String[] args) {
// 模拟订单数据
List<Order> orderList = Arrays.asList(
new Order("O1", "U1", new BigDecimal("100.5"), LocalDate.of(2024, 3, 15), true),
new Order("O2", "U1", new BigDecimal("200.8"), LocalDate.of(2024, 4, 20), true),
new Order("O3", "U2", new BigDecimal("150.3"), LocalDate.of(2023, 12, 5), true), // 2023年
new Order("O4", "U2", new BigDecimal("300.0"), LocalDate.of(2024, 5, 10), false), // 无效订单
new Order("O5", "U3", new BigDecimal("250.6"), LocalDate.of(2024, 6, 8), true),
new Order("O6", "U4", new BigDecimal("400.2"), LocalDate.of(2024, 2, 28), true),
new Order("O7", "U4", new BigDecimal("120.9"), LocalDate.of(2024, 7, 1), true)
);
// 数据处理流程
List<Map.Entry<String, BigDecimal>> top3User = orderList.stream()
// 1. 筛选2024年的有效订单
.filter(order -> order.isValid()
&& order.getCreateTime().getYear() == 2024)
// 2. 按用户ID分组,计算每个用户的总金额
.collect(Collectors.groupingBy(
Order::getUserId,
Collectors.reducing(
BigDecimal.ZERO,
Order::getAmount,
BigDecimal::add
)
))
// 3. 将Map转换为Entry流,按总金额降序排序
.entrySet().stream()
.sorted(Map.Entry.comparingByValue(Comparator.reverseOrder()))
// 4. 取前3名
.limit(3)
// 5. 收集结果
.collect(Collectors.toList());
// 输出结果
top3User.forEach(entry ->
System.out.println("用户ID:" + entry.getKey() + ",总金额:" + entry.getValue())
);
// 输出:
// 用户ID:U4,总金额:521.1
// 用户ID:U1,总金额:301.3
// 用户ID:U3,总金额:250.6
}
}Stream流一旦执行终止操作后就会被"消费",再次调用中间或终止操作会抛出IllegalStateException。
public class StreamConsumeDemo {
public static void main(String[] args) {
Stream<String> stream = Stream.of("a", "b", "c");
// 第一次终止操作
stream.forEach(System.out::println);
// 第二次操作:抛出异常
stream.filter(s -> s.length() > 0).forEach(System.out::println);
// 异常:java.lang.IllegalStateException: stream has already been operated upon or closed
}
}<font style="background-color:rgb(255,245,235);">解决方案</font><font style="background-color:rgb(255,245,235);">:若需多次处理数据,应重新创建流(如从原始集合创建新流)。</font>
中间操作仅记录逻辑,不实际执行,若中间操作存在副作用(如修改外部变量),可能导致预期外结果。
public class LazyEvaluateDemo {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3);
int count = 0;
Stream<Integer> stream = list.stream()
.filter(num -> {
count++; // 副作用:修改外部变量
return num > 1;
});
System.out.println("count before terminal: " + count); // 输出:0(未执行过滤)
stream.forEach(System.out::println); // 执行终止操作
System.out.println("count after terminal: " + count); // 输出:3(遍历了3个元素)
}
}<font style="background-color:rgb(255,245,235);">解决方案</font><font style="background-color:rgb(255,245,235);">:避免在中间操作中引入副作用,如需统计可使用终止操作(如count())。</font>
并行流使用Fork/Join框架实现,默认使用公共线程池,若在forEach中修改非线程安全的集合(如ArrayList),会导致数据错乱。下面通过案例复现问题,并提供实战解决方案。
public class ParallelStreamSafeDemo {
public static void main(String[] args) {
List<Integer> unsafeList = new ArrayList<>();
// 并行流操作非线程安全集合
IntStream.range(0, 10000).parallel()
.forEach(unsafeList::add);
System.out.println("非线程安全集合大小:" + unsafeList.size()); // 结果大概率小于10000(数据丢失/重复)
// 对比:串行流操作非线程安全集合(无问题)
List<Integer> serialList = new ArrayList<>();
IntStream.range(0, 10000).sequential()
.forEach(serialList::add);
System.out.println("串行流操作后集合大小:" + serialList.size()); // 稳定输出10000
}
}public class ParallelStreamSafeDemo {
public static void main(String[] args) {
List<Integer> unsafeList = new ArrayList<>();
// 并行流操作非线程安全集合
IntStream.range(0, 10000).parallel()
.forEach(unsafeList::add);
System.out.println("非线程安全集合大小:" + unsafeList.size()); // 结果大概率小于10000(数据丢失)
// 对比:串行流操作非线程安全集合(无问题)
List<Integer> serialList = new ArrayList<>();
IntStream.range(0, 10000).sequential()
.forEach(serialList::add);
System.out.println("串行流操作后集合大小:" + serialList.size()); // 稳定输出10000
}
}<font style="background-color:rgb(255,245,235);">核心解决方案</font><font style="background-color:rgb(255,245,235);">:推荐两种线程安全实现方式,根据场景选择</font>
Java提供CopyOnWriteArrayList、ConcurrentHashMap等线程安全集合,可直接在并行流中操作。需注意:CopyOnWriteArrayList通过“写时复制”实现安全,插入性能较低,适合读多写少场景。
Java提供了线程安全的集合类(如CopyOnWriteArrayList、ConcurrentLinkedQueue),可直接在并行流中操作。需注意:CopyOnWriteArrayList通过"写时复制"实现线程安全,插入性能较低,适合读多写少场景。
public class ParallelSafeSolution1 {
public static void main(String[] args) {
// 线程安全集合:CopyOnWriteArrayList
List<Integer> safeList = new CopyOnWriteArrayList<>();
IntStream.range(0, 10000).parallel()
.forEach(safeList::add);
System.out.println("线程安全集合大小:" + safeList.size()); // 稳定输出10000
}
}public class ParallelSafeSolution1 {
public static void main(String[] args) {
// 使用线程安全集合CopyOnWriteArrayList
List<Integer> safeList = new CopyOnWriteArrayList<>();
IntStream.range(0, 10000).parallel()
.forEach(safeList::add);
System.out.println("线程安全集合大小:" + safeList.size()); // 稳定输出10000
}
}Stream的collect()是内部迭代,底层通过“拆分-聚合”模式处理并行任务,自动保证线程安全,且性能优于直接使用线程安全集合(避免写时复制开销)。
Stream的collect()方法是内部迭代,底层会处理线程安全问题,无需手动指定线程安全集合,性能优于直接使用CopyOnWriteArrayList,是并行流收集结果的首选方式。
public class ParallelSafeSolution2 {
public static void main(String[] args) {
// 并行流+collect:天生线程安全
List<Integer> resultList = IntStream.range(0, 10000).parallel()
.boxed() // 转换为Stream<Integer>
.collect(Collectors.toList()); // 底层使用线程安全的容器收集
System.out.println("collect收集后大小:" + resultList.size()); // 稳定输出10000
}
}public class ParallelSafeSolution2 {
public static void main(String[] args) {
// 并行流中使用collect收集,天生线程安全
List<Integer> resultList = IntStream.range(0, 10000).parallel()
.boxed() // 转换为Stream<Integer>
.collect(Collectors.toList());
System.out.println("collect收集后集合大小:" + resultList.size()); // 稳定输出10000
}
}并行流中修改共享变量(如int计数器)同样存在安全问题,需用原子类或reduce()方法(无锁机制,性能更优)。
除了集合操作,并行流中修改共享变量(如int、long类型变量)也会出现线程安全问题,需使用原子类(AtomicInteger、AtomicLong)或通过reduce()方法实现。
public class ParallelSharedVarSafe {
public static void main(String[] args) {
// 错误:修改普通共享变量
int errorCount = 0;
IntStream.range(0, 10000).parallel().forEach(i -> errorCount++);
System.out.println("错误计数:" + errorCount); // 结果<10000
// 正确1:使用原子类(AtomicInteger)
AtomicInteger atomicCount = new AtomicInteger(0);
IntStream.range(0, 10000).parallel().forEach(i -> atomicCount.incrementAndGet());
System.out.println("原子类计数:" + atomicCount.get()); // 10000
// 正确2:使用reduce()(推荐,无锁)
int reduceCount = IntStream.range(0, 10000).parallel()
.reduce(0, (acc, i) -> acc + 1); // 初始值0,累加逻辑
System.out.println("reduce计数:" + reduceCount); // 10000
}
}public class ParallelSharedVarSafe {
public static void main(String[] args) {
// 错误示范:修改共享变量
int count = 0;
IntStream.range(0, 10000).parallel()
.forEach(i -> count++); // 线程不安全,结果小于10000
System.out.println("错误计数:" + count);
// 正确示范1:使用原子类
AtomicInteger atomicCount = new AtomicInteger(0);
IntStream.range(0, 10000).parallel()
.forEach(i -> atomicCount.incrementAndGet());
System.out.println("原子类计数:" + atomicCount.get()); // 10000
// 正确示范2:使用reduce()(更推荐,无锁机制)
int reduceCount = IntStream.range(0, 10000).parallel()
.reduce(0, (acc, i) -> acc + 1);
System.out.println("reduce计数:" + reduceCount); // 10000
}
}实战考点:判断“一段Stream代码是否会执行”(如仅写中间操作不执行)。
<font style="background-color:rgb(255,245,235);">避坑总结</font><font style="background-color:rgb(255,245,235);">:并行流安全的核心是“避免手动操作外部非安全对象”,优先用</font><font style="background-color:rgb(255,245,235);">collect()</font><font style="background-color:rgb(255,245,235);">收集结果、</font><font style="background-color:rgb(255,245,235);">reduce()</font><font style="background-color:rgb(255,245,235);">聚合数据,减少原子类使用(有锁开销)。</font>
答案:这道题考察对Stream执行机制的核心理解,需从“执行触发”“返回值”“底层逻辑”三个维度区分,同时结合案例验证惰性求值。
维度 | 中间操作 | 终止操作 |
|---|---|---|
执行触发 | 惰性求值,仅记录操作逻辑,不实际执行 | 触发执行,一次性执行所有中间操作链 |
返回值类型 | 返回Stream对象,支持链式调用 | 返回非Stream类型(如List、Boolean、Long) |
典型示例 | filter()、map()、sorted() | collect()、forEach()、count() |
通过中间操作中添加打印逻辑,观察是否执行:
public class LazyEvaluateTest {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1,2,3,4);
// 仅添加中间操作,无终止操作
Stream<Integer> stream = list.stream()
.filter(num -> {
System.out.println("执行过滤:" + num); // 若不打印,说明未执行
return num % 2 == 0;
});
System.out.println("未调用终止操作,中间操作未执行");
// 调用终止操作
stream.collect(Collectors.toList());
System.out.println("调用终止操作后,中间操作执行");
}
}输出结果: 未调用终止操作,中间操作未执行 执行过滤:1 执行过滤:2 执行过滤:3 执行过滤:4 调用终止操作后,中间操作执行
判断“仅写中间操作是否会触发数据处理”“链式调用中操作的执行顺序”。
答案:这道题考察并行流实战避坑能力,需明确问题根源、核心解决方案及适用场景。
并行流基于Fork/Join框架,使用公共线程池拆分任务,若操作非线程安全的外部对象(如ArrayList、普通int变量),会出现数据丢失、重复或错乱,根源是多线程并发修改共享资源未加同步。
public class ParallelUnsafeDemo {
public static void main(String[] args) {
List<Integer> unsafeList = new ArrayList<>();
// 并行流操作非线程安全集合
IntStream.range(0, 10000).parallel()
.forEach(unsafeList::add);
System.out.println("实际大小:" + unsafeList.size()); // 大概率<10000(数据丢失)
}
}优先使用Stream原生安全机制,避免手动加锁,推荐2种核心方案:
List<Integer> safeList = IntStream.range(0, 10000).parallel()
.boxed()
.collect(Collectors.toList()); // 天生安全
System.out.println("安全大小:" + safeList.size()); // 稳定10000List<Integer> safeList = new CopyOnWriteArrayList<>();
IntStream.range(0, 10000).parallel()
.forEach(safeList::add);
System.out.println("安全大小:" + safeList.size()); // 稳定10000区分“collect()与线程安全集合的性能差异”“并行流与共享变量的安全处理(如用AtomicInteger或reduce())”。
答案:这道题考察Stream映射操作的深度理解,核心是“处理一对一”与“一对多”的差异。
维度 | map(Function<T,R>) | flatMap(Function<T,Stream<R>>) |
|---|---|---|
映射关系 | 一对一:将T类型转为R类型(非流) | 一对多:将T类型转为Stream<R>(流) |
核心作用 | 类型转换、属性提取 | 扁平化:拆分嵌套流为单个流 |
返回流类型 | Stream<R> | Stream<R>(无嵌套) |
需求:处理“单词列表”,提取所有不重复的字母(单词内部拆分为字母,需去重)。
public class MapVsFlatMapError {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana");
// map()返回Stream<Stream<Character>>(嵌套流,无法直接去重)
Stream<Stream<Character>> nestedStream = words.stream()
.map(word -> word.chars().mapToObj(c -> (char) c));
// 无法直接调用distinct(),需额外处理嵌套
}
}public class MapVsFlatMapCorrect {
public static void main(String[] args) {
List<String> words = Arrays.asList("apple", "banana");
// flatMap()拆分并合并为单个Stream<Character>
List<Character> uniqueChars = words.stream()
.flatMap(word -> word.chars().mapToObj(c -> (char) c)) // 拆分为字母流
.distinct() // 直接去重
.collect(Collectors.toList());
System.out.println(uniqueChars); // 输出:[a, p, l, e, b, n]
}
}Stream流作为Java8里程碑式的特性,以声明式编程+惰性求值为核心设计,彻底优化了集合数据处理的效率与代码可读性。结合前文实战案例与高频面试考点,核心总结如下:
collect()(底层拆分聚合,无锁高效),读多写少场景可选CopyOnWriteArrayList,避免直接操作外部共享变量。map(),嵌套流/集合扁平化(如拆分单词为字母)用flatMap(),核心解决“流嵌套”问题。从“特性本质→操作组合→实战避坑”逐步深入:先吃透惰性求值、并行机制等底层逻辑,再熟练掌握filter+map+collect等高频组合,最终结合业务场景(如订单统计、数据清洗)落地,才能真正发挥其高效开发价值。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。