Java 8 Stream 流全面使用教程 - 完整版
目录
Stream 基础概念与特性
什么是Stream?
Stream的核心特性
Stream操作的分类
Stream 创建方式大全
1. 从集合创建Stream
2. 从数组创建Stream
3. 生成Stream的各种方法
4. 从文件和其他资源创建Stream
中间操作详解
1. filter() - 过滤操作
2. map() - 映射转换操作
3. flatMap() - 扁平化映射操作
4. distinct(), sorted(), peek(), limit(), skip()
终端操作详解
1. collect() - 收集操作
2. reduce() - 归约操作
3. 匹配操作 (anyMatch, allMatch, noneMatch)
4. 查找操作 (findFirst, findAny)
实际开发中的常用模式
1. 查找并提取属性的模式
2. 数据转换和映射模式
3. 过滤和验证模式
收集器Collectors详解
1. 基础收集器
2. 分组收集器 groupingBy
3. 分区收集器 partitioningBy
4. 字符串收集器和数值收集器
并行流与性能优化
1. 并行流基础
Stream 基础概念与特性
什么是Stream?
Stream是Java 8引入的函数式编程API,用于对集合数据进行声明式处理。
// 传统方式:命令式编程
List<String> names = new ArrayList<>();
for (Person person : people) {if (person.getAge() > 18) {names.add(person.getName().toUpperCase());}
}// Stream方式:声明式编程
List<String> names = people.stream().filter(person -> person.getAge() > 18) // 过滤成年人.map(person -> person.getName().toUpperCase()) // 转换为大写姓名.collect(Collectors.toList()); // 收集为列表
Stream的核心特性
/*** Stream的四大特性演示*/
public class StreamCharacteristics {public static void demonstrateCharacteristics() {List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 1. 不存储数据 - Stream不是数据结构,不存储元素Stream<Integer> stream = numbers.stream(); // 只是数据的视图// 2. 函数式编程 - 不修改原数据源List<Integer> doubled = numbers.stream().map(n -> n * 2) // 映射操作不改变原集合.collect(Collectors.toList());System.out.println("原集合: " + numbers); // [1, 2, 3, 4, 5]System.out.println("新集合: " + doubled); // [2, 4, 6, 8, 10]// 3. 惰性求值 - 中间操作延迟执行,只有终端操作才触发计算Stream<Integer> lazyStream = numbers.stream().filter(n -> {System.out.println("过滤: " + n); // 这里不会执行return n % 2 == 0;}).map(n -> {System.out.println("映射: " + n); // 这里也不会执行return n * 2;});// 上面的代码不会有任何输出,因为没有终端操作System.out.println("开始执行终端操作:");List<Integer> result = lazyStream.collect(Collectors.toList()); // 现在才执行// 4. 可消费性 - Stream只能使用一次Stream<Integer> onceStream = numbers.stream();onceStream.count(); // 第一次使用// onceStream.findFirst(); // 错误!Stream已经被消费了}
}
Stream操作的分类
/*** Stream操作分为中间操作和终端操作*/
public class StreamOperationTypes {// 中间操作 (Intermediate Operations) - 返回Stream,支持链式调用public static void intermediateOperations() {List<String> words = Arrays.asList("apple", "banana", "cherry");Stream<String> processed = words.stream().filter(word -> word.length() > 5) // 中间操作:过滤.map(String::toUpperCase) // 中间操作:映射.distinct() // 中间操作:去重.sorted(); // 中间操作:排序// 注意:上面的代码不会执行任何操作,因为没有终端操作}// 终端操作 (Terminal Operations) - 产生结果,触发Stream执行public static void terminalOperations() {List<String> words = Arrays.asList("apple", "banana", "cherry");// 不同的终端操作List<String> collected = words.stream().filter(word -> word.length() > 5).collect(Collectors.toList()); // 终端操作:收集long count = words.stream().filter(word -> word.length() > 5).count(); // 终端操作:计数boolean anyMatch = words.stream().anyMatch(word -> word.startsWith("a")); // 终端操作:匹配words.stream().filter(word -> word.length() > 5).forEach(System.out::println); // 终端操作:遍历}
}
Stream 创建方式大全
1. 从集合创建Stream
/*** 从各种集合类型创建Stream*/
public class CollectionToStream {public static void createFromCollections() {// 从List创建List<String> list = Arrays.asList("apple", "banana", "cherry");Stream<String> listStream = list.stream(); // 顺序流Stream<String> parallelStream = list.parallelStream(); // 并行流// 从Set创建Set<Integer> set = new HashSet<>(Arrays.asList(1, 2, 3, 4, 5));Stream<Integer> setStream = set.stream();// 从Map创建不同类型的流Map<String, Integer> map = new HashMap<>();map.put("apple", 5);map.put("banana", 6);map.put("cherry", 6);Stream<String> keyStream = map.keySet().stream(); // 键流Stream<Integer> valueStream = map.values().stream(); // 值流Stream<Map.Entry<String, Integer>> entryStream = map.entrySet().stream(); // 键值对流// 从Map中提取特定信息List<String> fruitsWithSixLetters = map.entrySet().stream().filter(entry -> entry.getValue() == 6) // 过滤长度为6的水果.map(Map.Entry::getKey) // 提取水果名称.collect(Collectors.toList());System.out.println("六个字母的水果: " + fruitsWithSixLetters);}
}
2. 从数组创建Stream
/*** 从数组创建各种类型的Stream*/
public class ArrayToStream {public static void createFromArrays() {// 从对象数组创建String[] stringArray = {"apple", "banana", "cherry"};Stream<String> stringStream = Arrays.stream(stringArray);// 从数组的指定范围创建Stream<String> rangeStream = Arrays.stream(stringArray, 1, 3); // [banana, cherry]// 从基本类型数组创建int[] intArray = {1, 2, 3, 4, 5};IntStream intStream = Arrays.stream(intArray);// 从数组创建并处理double[] prices = {9.99, 19.99, 29.99, 39.99};double averagePrice = Arrays.stream(prices).average() // 计算平均值.orElse(0.0); // 提供默认值System.out.println("平均价格: " + averagePrice);// 使用Stream.of()从可变参数创建Stream<String> varArgStream = Stream.of("red", "green", "blue");Stream<Integer> numberStream = Stream.of(1, 2, 3, 4, 5);}
}
3. 生成Stream的各种方法
/*** 使用不同方法生成Stream*/
public class StreamGeneration {public static void generateStreams() {// 1. 空StreamStream<String> emptyStream = Stream.empty();// 2. 单个元素的StreamStream<String> singleElementStream = Stream.of("单个元素");// 3. 使用Stream.builder()构建Stream<String> builtStream = Stream.<String>builder().add("第一个元素").add("第二个元素").add("第三个元素").build();// 4. 生成无限流 - Stream.generate()Stream<Double> randomNumbers = Stream.generate(Math::random).limit(5); // 限制为5个随机数Stream<String> constantStream = Stream.generate(() -> "Hello").limit(3); // 生成3个"Hello"// 5. 迭代生成 - Stream.iterate()Stream<Integer> evenNumbers = Stream.iterate(0, n -> n + 2).limit(10); // 生成前10个偶数: 0,2,4,6,8,10,12,14,16,18// 生成斐波那契数列Stream<Long> fibonacci = Stream.iterate(new long[]{0, 1}, array -> new long[]{array[1], array[0] + array[1]}).mapToLong(array -> array[0]) // 提取数列值.limit(20) // 前20个斐波那契数.boxed(); // 转换为Long对象流// Java 9+ 带条件的iterate// Stream<Integer> limitedIterate = Stream.iterate(1, n -> n <= 100, n -> n + 1);// 打印生成的数据进行验证System.out.println("前10个偶数:");Stream.iterate(0, n -> n + 2).limit(10).forEach(System.out::println);System.out.println("前10个斐波那契数:");Stream.iterate(new long[]{0, 1}, arr -> new long[]{arr[1], arr[0] + arr[1]}).mapToLong(arr -> arr[0]).limit(10).forEach(System.out::println);}
}
4. 从文件和其他资源创建Stream
import java.nio.file.*;
import java.util.regex.Pattern;/*** 从文件和其他外部资源创建Stream*/
public class FileAndResourceStreams {public static void createFromFiles() {try {// 1. 从文件读取行创建StreamStream<String> fileLines = Files.lines(Paths.get("data.txt"));// 注意:需要使用try-with-resources或手动关闭流// 2. 安全的文件读取方式try (Stream<String> lines = Files.lines(Paths.get("application.log"))) {List<String> errorLines = lines.filter(line -> line.contains("ERROR")) // 过滤错误日志.limit(100) // 只取前100条.collect(Collectors.toList());System.out.println("错误日志条数: " + errorLines.size());}// 3. 遍历目录创建文件流try (Stream<Path> paths = Files.walk(Paths.get("src"))) {List<String> javaFiles = paths.filter(Files::isRegularFile) // 只要文件,不要目录.filter(path -> path.toString().endsWith(".java")) // 只要Java文件.map(Path::toString) // 转换为字符串路径.collect(Collectors.toList());System.out.println("Java文件数量: " + javaFiles.size());}// 4. 查找文件try (Stream<Path> foundPaths = Files.find(Paths.get("src"), Integer.MAX_VALUE, // 搜索深度(path, basicFileAttributes) -> path.getFileName().toString().contains("Test"))) {foundPaths.forEach(System.out::println);}} catch (Exception e) {System.err.println("文件操作错误: " + e.getMessage());}}public static void createFromOtherSources() {// 5. 从字符串创建字符流IntStream charStream = "Hello World".chars();String upperCaseLetters = charStream.filter(Character::isUpperCase) // 过滤大写字母.mapToObj(c -> (char) c) // 转换为字符.map(String::valueOf) // 转换为字符串.collect(Collectors.joining()); // 连接字符串// 6. 从正则表达式分割创建流Pattern pattern = Pattern.compile(",");Stream<String> splitStream = pattern.splitAsStream("apple,banana,cherry,date");List<String> fruits = splitStream.collect(Collectors.toList());// 7. 从范围创建数值流IntStream range1 = IntStream.range(1, 10); // [1,2,3,4,5,6,7,8,9]IntStream range2 = IntStream.rangeClosed(1, 10); // [1,2,3,4,5,6,7,8,9,10]LongStream longRange = LongStream.rangeClosed(1L, 1000000L);// 8. 从Optional创建流 (Java 9+)Optional<String> optional = Optional.of("Hello");// Stream<String> optionalStream = optional.stream(); // Java 9+// Java 8中从Optional创建流的替代方法Stream<String> optionalStream = optional.map(Stream::of) // 如果有值则创建单元素流.orElse(Stream.empty()); // 否则创建空流}
}
中间操作详解
1. filter() - 过滤操作
/*** filter()操作的各种使用场景*/
public class FilterOperations {public static void demonstrateFilter() {List<Person> people = Arrays.asList(new Person("张三", 25, "工程师", 8000),new Person("李四", 30, "设计师", 7000),new Person("王五", 35, "经理", 12000),new Person("赵六", 28, "工程师", 9000),new Person("孙七", 22, "实习生", 3000));// 1. 基本过滤 - 过滤年龄大于25的人List<Person> adults = people.stream().filter(person -> person.getAge() > 25) // 年龄条件.collect(Collectors.toList());// 2. 多条件过滤 - 年龄大于25且薪水大于8000List<Person> seniorHighPaid = people.stream().filter(person -> person.getAge() > 25) // 第一个条件.filter(person -> person.getSalary() > 8000) // 第二个条件.collect(Collectors.toList());// 或者合并为一个条件List<Person> seniorHighPaid2 = people.stream().filter(person -> person.getAge() > 25 && person.getSalary() > 8000).collect(Collectors.toList());// 3. 字符串过滤List<String> words = Arrays.asList("apple", "banana", "cherry", "date", "elderberry");// 过滤长度大于5的单词List<String> longWords = words.stream().filter(word -> word.length() > 5).collect(Collectors.toList());// 过滤以特定字母开头的单词List<String> wordsStartingWithA = words.stream().filter(word -> word.startsWith("a")).collect(Collectors.toList());// 4. 空值过滤List<String> wordsWithNulls = Arrays.asList("apple", null, "banana", "", "cherry", null);List<String> nonNullNonEmpty = wordsWithNulls.stream().filter(Objects::nonNull) // 过滤非null.filter(word -> !word.isEmpty()) // 过滤非空字符串.collect(Collectors.toList());// 5. 使用方法引用进行过滤List<String> validEmails = Arrays.asList("user@example.com", "invalid-email", "test@test.com", "bad@");List<String> emails = validEmails.stream().filter(FilterOperations::isValidEmail) // 使用自定义方法.collect(Collectors.toList());// 6. 复杂对象属性过滤List<Person> engineers = people.stream().filter(person -> "工程师".equals(person.getJob())) // 职业过滤.collect(Collectors.toList());// 7. 基于集合的过滤Set<String> allowedJobs = Set.of("工程师", "设计师");List<Person> allowedPeople = people.stream().filter(person -> allowedJobs.contains(person.getJob())).collect(Collectors.toList());System.out.println("过滤后的工程师: " + engineers.size() + "人");}// 自定义验证方法private static boolean isValidEmail(String email) {return email != null && email.contains("@") && email.contains(".") &&email.indexOf("@") < email.lastIndexOf(".");}
}
2. map() - 映射转换操作
/*** map()操作的各种转换场景*/
public class MapOperations {public static void demonstrateMap() {List<Person> people = Arrays.asList(new Person("张三", 25, "工程师", 8000),new Person("李四", 30, "设计师", 7000),new Person("王五", 35, "经理", 12000));// 1. 基本类型转换 - 提取姓名List<String> names = people.stream().map(Person::getName) // 方法引用提取姓名.collect(Collectors.toList());// 2. 数值计算转换 - 计算年薪List<Integer> annualSalaries = people.stream().map(person -> person.getSalary() * 12) // 月薪转年薪.collect(Collectors.toList());// 3. 字符串转换操作List<String> words = Arrays.asList("apple", "banana", "cherry");// 转换为大写List<String> upperCaseWords = words.stream().map(String::toUpperCase) // 方法引用转大写.collect(Collectors.toList());// 获取字符串长度List<Integer> wordLengths = words.stream().map(String::length) // 获取长度.collect(Collectors.toList());// 字符串格式化List<String> formattedWords = words.stream().map(word -> String.format("[%s]", word.toUpperCase())).collect(Collectors.toList());// 4. 复杂对象转换 - 创建DTO对象List<PersonDTO> personDTOs = people.stream().map(person -> new PersonDTO(person.getName().toUpperCase(), // 姓名大写person.getAge(),person.getJob(),person.getSalary() * 12 // 年薪)).collect(Collectors.toList());// 5. 链式转换List<String> processedNames = people.stream().map(Person::getName) // 提取姓名.map(String::toUpperCase) // 转大写.map(name -> "Mr/Ms. " + name) // 添加前缀.collect(Collectors.toList());// 6. 条件转换List<String> salaryLevels = people.stream().map(person -> {if (person.getSalary() > 10000) {return person.getName() + " - 高薪";} else if (person.getSalary() > 7000) {return person.getName() + " - 中薪";} else {return person.getName() + " - 低薪";}}).collect(Collectors.toList());// 7. 数学运算转换List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);// 平方List<Integer> squares = numbers.stream().map(n -> n * n).collect(Collectors.toList());// 数学函数应用List<Double> logarithms = numbers.stream().map(Integer::doubleValue) // 转换为double.map(Math::log) // 计算对数.collect(Collectors.toList());// 8. 复杂映射 - 从对象中提取多个属性组合List<String> personInfo = people.stream().map(person -> String.format("%s (%d岁, %s, ¥%d)", person.getName(), person.getAge(), person.getJob(),person.getSalary())).collect(Collectors.toList());System.out.println("人员信息: " + personInfo);}
}// DTO类用于演示对象转换
class PersonDTO {private String name;private int age;private String job;private int annualSalary;public PersonDTO(String name, int age, String job, int annualSalary) {this.name = name;this.age = age;this.job = job;this.annualSalary = annualSalary;}// getters and toString...
}
3. flatMap() - 扁平化映射操作
/*** flatMap()操作用于处理嵌套结构和扁平化数据*/
public class FlatMapOperations {public static void demonstrateFlatMap() {// 1. 基本扁平化 - 二维列表转一维List<List<String>> listOfLists = Arrays.asList(Arrays.asList("苹果", "香蕉"),Arrays.asList("樱桃", "枣子"),Arrays.asList("接骨木果", "无花果"));// 扁平化为单一列表List<String> flattenedFruits = listOfLists.stream().flatMap(Collection::stream) // 将每个内部列表展开.collect(Collectors.toList());System.out.println("扁平化水果: " + flattenedFruits);// 2. 字符串分割扁平化List<String> sentences = Arrays.asList("Java 是一种编程语言","Stream 是 Java8 新特性", "函数式编程很强大");// 将句子分割成单词List<String> words = sentences.stream().flatMap(sentence -> Arrays.stream(sentence.split(" &