java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > Java Stream流

Java Stream流从入门到精通

作者:待畔逢

Stream(流)是Java 8引入的一个重要特性,它提供了一种声明式的数据处理方式,本文给大家介绍Java Stream流从入门到精通,感兴趣的朋友跟随小编一起看看吧

Java Stream流详解:从入门到精通

1. Stream流简介

什么是Stream?

Stream(流)是Java 8引入的一个重要特性,它提供了一种声明式的数据处理方式。Stream不是数据结构,而是对数据源(如集合、数组等)进行操作的抽象。

为什么要使用Stream?

传统的集合操作往往需要编写大量的循环代码,而Stream提供了更加简洁、易读的函数式编程风格:

// 传统方式:找出年龄大于18的用户姓名
List<String> result = new ArrayList<>();
for (User user : users) {
    if (user.getAge() > 18) {
        result.add(user.getName());
    }
}
// Stream方式
List<String> result = users.stream()
    .filter(user -> user.getAge() > 18)
    .map(User::getName)
    .collect(Collectors.toList());

2. Stream的特点

2.1 主要特征

  1. 不存储数据:Stream不是数据结构,不会存储元素
  2. 函数式编程:支持链式调用,代码更简洁
  3. 惰性求值:中间操作不会立即执行,只有遇到终端操作才会执行
  4. 可消费性:Stream只能被消费一次

2.2 Stream操作分类

// 中间操作(Intermediate Operations)
// 返回Stream对象,支持链式调用
stream.filter().map().sorted()
// 终端操作(Terminal Operations)  
// 返回具体结果,触发流的执行
stream.collect().forEach().reduce()

3. 创建Stream流

3.1 从集合创建

List<String> list = Arrays.asList("a", "b", "c");
// 串行流
Stream<String> stream = list.stream();
// 并行流
Stream<String> parallelStream = list.parallelStream();

3.2 从数组创建

String[] array = {"a", "b", "c"};
Stream<String> stream = Arrays.stream(array);
// 指定范围
Stream<String> stream2 = Arrays.stream(array, 1, 3);

3.3 直接创建

// 通过Stream.of()
Stream<String> stream = Stream.of("a", "b", "c");
// 创建空流
Stream<String> empty = Stream.empty();
// 创建无限流
Stream<Integer> iterate = Stream.iterate(0, n -> n + 2);
Stream<Double> generate = Stream.generate(Math::random);

3.4 从文件创建

try (Stream<String> lines = Files.lines(Paths.get("file.txt"))) {
    lines.forEach(System.out::println);
} catch (IOException e) {
    e.printStackTrace();
}

4. 中间操作详解

4.1 filter() - 过滤

根据条件过滤元素:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 过滤出偶数
List<Integer> evenNumbers = numbers.stream()
    .filter(n -> n % 2 == 0)
    .collect(Collectors.toList());
// 结果:[2, 4, 6, 8, 10]

4.2 map() - 映射转换

将元素转换为其他形式:

List<String> words = Arrays.asList("hello", "world", "java");
// 转换为大写
List<String> upperCase = words.stream()
    .map(String::toUpperCase)
    .collect(Collectors.toList());
// 结果:["HELLO", "WORLD", "JAVA"]
// 获取字符串长度
List<Integer> lengths = words.stream()
    .map(String::length)
    .collect(Collectors.toList());
// 结果:[5, 5, 4]

4.3 flatMap() - 扁平化映射

将嵌套结构扁平化:

List<List<String>> nested = Arrays.asList(
    Arrays.asList("a", "b"),
    Arrays.asList("c", "d"),
    Arrays.asList("e", "f")
);
// 扁平化处理
List<String> flattened = nested.stream()
    .flatMap(Collection::stream)
    .collect(Collectors.toList());
// 结果:["a", "b", "c", "d", "e", "f"]

4.4 distinct() - 去重

List<Integer> numbers = Arrays.asList(1, 2, 2, 3, 3, 4, 5);
List<Integer> unique = numbers.stream()
    .distinct()
    .collect(Collectors.toList());
// 结果:[1, 2, 3, 4, 5]

4.5 sorted() - 排序

List<String> words = Arrays.asList("banana", "apple", "cherry");
// 自然排序
List<String> sorted = words.stream()
    .sorted()
    .collect(Collectors.toList());
// 结果:["apple", "banana", "cherry"]
// 自定义排序
List<String> sortedByLength = words.stream()
    .sorted(Comparator.comparing(String::length))
    .collect(Collectors.toList());
// 结果:["apple", "banana", "cherry"]

4.6 limit() 和 skip()

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 限制元素数量
List<Integer> limited = numbers.stream()
    .limit(5)
    .collect(Collectors.toList());
// 结果:[1, 2, 3, 4, 5]
// 跳过前几个元素
List<Integer> skipped = numbers.stream()
    .skip(5)
    .collect(Collectors.toList());
// 结果:[6, 7, 8, 9, 10]
// 分页效果:跳过前5个,取3个
List<Integer> page = numbers.stream()
    .skip(5)
    .limit(3)
    .collect(Collectors.toList());
// 结果:[6, 7, 8]

5. 终端操作详解

5.1 collect() - 收集结果

List<String> words = Arrays.asList("apple", "banana", "cherry");
// 收集到List
List<String> list = words.stream().collect(Collectors.toList());
// 收集到Set
Set<String> set = words.stream().collect(Collectors.toSet());
// 收集到Map
Map<String, Integer> map = words.stream()
    .collect(Collectors.toMap(
        word -> word,           // key
        String::length          // value
    ));
// 分组
Map<Integer, List<String>> grouped = words.stream()
    .collect(Collectors.groupingBy(String::length));
// 连接字符串
String joined = words.stream()
    .collect(Collectors.joining(", "));
// 结果:"apple, banana, cherry"

5.2 forEach() - 遍历

List<String> words = Arrays.asList("apple", "banana", "cherry");
// 打印每个元素
words.stream().forEach(System.out::println);
// 并行遍历(注意:顺序不保证)
words.parallelStream().forEach(System.out::println);
// 有序遍历
words.parallelStream().forEachOrdered(System.out::println);

5.3 reduce() - 归约

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 求和
Optional<Integer> sum = numbers.stream()
    .reduce((a, b) -> a + b);
// 带初始值的求和
Integer sum2 = numbers.stream()
    .reduce(0, (a, b) -> a + b);
// 求最大值
Optional<Integer> max = numbers.stream()
    .reduce(Integer::max);
// 字符串连接
List<String> words = Arrays.asList("Hello", " ", "World");
String result = words.stream()
    .reduce("", String::concat);
// 结果:"Hello World"

5.4 查找操作

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 查找任意一个偶数
Optional<Integer> any = numbers.stream()
    .filter(n -> n % 2 == 0)
    .findAny();
// 查找第一个偶数
Optional<Integer> first = numbers.stream()
    .filter(n -> n % 2 == 0)
    .findFirst();

5.5 匹配操作

List<Integer> numbers = Arrays.asList(2, 4, 6, 8);
// 是否所有元素都是偶数
boolean allEven = numbers.stream()
    .allMatch(n -> n % 2 == 0);  // true
// 是否存在偶数
boolean anyEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0);  // true
// 是否没有奇数
boolean noneOdd = numbers.stream()
    .noneMatch(n -> n % 2 == 1); // true

5.6 统计操作

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
// 计数
long count = numbers.stream().count();
// 数值流统计
IntSummaryStatistics stats = numbers.stream()
    .mapToInt(Integer::intValue)
    .summaryStatistics();
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
System.out.println("Min: " + stats.getMin());

6. 实际应用案例

6.1 用户数据处理

public class User {
    private String name;
    private int age;
    private String city;
    private double salary;
    // 构造函数、getter、setter省略
}
List<User> users = Arrays.asList(
    new User("张三", 25, "北京", 8000),
    new User("李四", 30, "上海", 12000),
    new User("王五", 22, "北京", 6000),
    new User("赵六", 28, "深圳", 15000)
);
// 案例1:找出年龄大于25岁,在北京工作的用户姓名
List<String> result1 = users.stream()
    .filter(user -> user.getAge() > 25)
    .filter(user -> "北京".equals(user.getCity()))
    .map(User::getName)
    .collect(Collectors.toList());
// 案例2:按城市分组统计平均工资
Map<String, Double> avgSalaryByCity = users.stream()
    .collect(Collectors.groupingBy(
        User::getCity,
        Collectors.averagingDouble(User::getSalary)
    ));
// 案例3:找出工资最高的用户
Optional<User> highestPaid = users.stream()
    .max(Comparator.comparing(User::getSalary));
// 案例4:计算所有用户的总工资
double totalSalary = users.stream()
    .mapToDouble(User::getSalary)
    .sum();

6.2 文件处理

// 读取文件并统计单词频率
public Map<String, Long> countWords(String filePath) {
    try (Stream<String> lines = Files.lines(Paths.get(filePath))) {
        return lines
            .flatMap(line -> Arrays.stream(line.split("\\s+")))
            .filter(word -> !word.isEmpty())
            .map(String::toLowerCase)
            .collect(Collectors.groupingBy(
                Function.identity(),
                Collectors.counting()
            ));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

6.3 数据转换

// 将CSV数据转换为对象
public List<Product> parseCSV(List<String> csvLines) {
    return csvLines.stream()
        .skip(1) // 跳过标题行
        .map(line -> line.split(","))
        .filter(fields -> fields.length >= 3)
        .map(fields -> new Product(
            fields[0].trim(),
            Double.parseDouble(fields[1].trim()),
            Integer.parseInt(fields[2].trim())
        ))
        .collect(Collectors.toList());
}

7. 性能优化建议

7.1 选择合适的流类型

// 对于大数据集,考虑使用并行流
List<Integer> largeList = IntStream.range(0, 1000000)
    .boxed()
    .collect(Collectors.toList());
// 串行流
long serialTime = System.currentTimeMillis();
long serialSum = largeList.stream()
    .mapToLong(Integer::longValue)
    .sum();
serialTime = System.currentTimeMillis() - serialTime;
// 并行流
long parallelTime = System.currentTimeMillis();
long parallelSum = largeList.parallelStream()
    .mapToLong(Integer::longValue)
    .sum();
parallelTime = System.currentTimeMillis() - parallelTime;

7.2 避免装箱拆箱

// 不推荐:会产生装箱拆箱开销
int sum1 = numbers.stream()
    .mapToInt(Integer::intValue)
    .sum();
// 推荐:直接使用基本类型流
int sum2 = IntStream.of(1, 2, 3, 4, 5).sum();

7.3 合理使用短路操作

// 使用短路操作提高效率
boolean hasEven = numbers.stream()
    .anyMatch(n -> n % 2 == 0); // 找到第一个偶数就返回
// 限制处理数量
List<String> first10 = largeStringList.stream()
    .filter(s -> s.length() > 5)
    .limit(10)
    .collect(Collectors.toList());

8. 常见陷阱与注意事项

8.1 Stream只能使用一次

Stream<String> stream = Stream.of("a", "b", "c");
stream.forEach(System.out::println); // 正常
// stream.count(); // 抛出IllegalStateException

8.2 并行流的线程安全

// 错误:非线程安全的操作
List<String> result = new ArrayList<>();
words.parallelStream().forEach(result::add); // 可能导致数据丢失
// 正确:使用线程安全的收集器
List<String> result = words.parallelStream()
    .collect(Collectors.toList());

8.3 注意null值处理

List<String> words = Arrays.asList("hello", null, "world");
// 过滤null值
List<String> filtered = words.stream()
    .filter(Objects::nonNull)
    .collect(Collectors.toList());

9. 总结

Stream流是Java 8引入的强大特性,它提供了:

优点

适用场景

最佳实践

  1. 优先使用Stream API替代传统循环
  2. 合理选择串行流和并行流
  3. 注意Stream的一次性使用特性
  4. 使用基本类型流避免装箱开销
  5. 善用短路操作提高性能

通过掌握Stream流,你可以写出更加简洁、高效、易维护的Java代码。在实际开发中,Stream已经成为处理集合数据的标准方式,值得每个Java开发者深入学习和应用。

到此这篇关于Java Stream流从入门到精通的文章就介绍到这了,更多相关Java Stream流内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文