目录
4. distinct(), sorted(), peek(), limit(), skip()
3. 匹配操作 (anyMatch, allMatch, noneMatch)
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. 空Stream
Stream<String> emptyStream = Stream.empty();
// 2. 单个元素的Stream
Stream<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. 从文件读取行创建Stream
Stream<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且薪水大于8000
List<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(
"[email protected]", "invalid-email", "[email protected]", "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(" "))) // 分割并扁平化
.collect(Coll