Java 8 Stream API 基础教程

作者:禅与计算机程序设计艺术

1.简介

1995年4月1日,Java编程语言问世,被广泛应用于各个领域。Java平台提供了面向对象的编程能力、安全性、健壮性和可移植性。作为第一个支持动态类型和自动内存管理的多范型语言,Java在后续版本中不断增加新的功能特性,如Java SE 7、Java SE 8等。其中,Java 8也是一个值得关注的版本,因为它提供了一个全新的Stream API。

Stream API是Java 8中新增的一个集合处理接口,其目的是对集合元素进行各种操作,如过滤、排序、映射、聚合等。Stream 的操作可以是串行的或者并行的,通过 Stream API 可以极大地提高编程效率。

1998年,Sun公司推出了Java 2 Platform(简称J2EE)规范,其中包括了对 Collections Framework 的改进。为了更好地利用多核CPU,Java开发者们又引入了多线程编程技术,而引入了Fork/Join框架。由于Java一直是一个跨平台的语言,所以可以运行在各种不同类型的平台上。

在新的时间轴上,Java社区经历了一场深刻的变革。高性能计算成为一个重要的市场需求,因此出现了Parallel Streams。很多框架如 Apache Spark,基于流模型实现并行计算。此外,还有一些大数据处理框架如 Hadoop,Flume等,也是使用流式数据处理。

流式处理的主要优点在于并行性和易用性,可以在短时间内完成复杂的数据分析任务。同时,流式编程也能够帮助开发者编写更加简洁、高效的代码。

2.前言
本文档将详细介绍Java 8中的流式处理API——Stream API的相关知识。阅读本文档,你可以了解到如下内容:

  • 对Java 8中Stream API的基础认识;
  • 学习如何使用Stream API进行数据处理;
  • 掌握Java 8 Stream API的最新特性,包括Lambda表达式及函数式接口;
  • 理解流式处理的特点和适用场景。

您需要具备以下基础知识才能理解本文档的内容:

  • 熟悉Java语言的基本语法规则;
  • 具有扎实的计算机科学理论基础,如算法和数据结构;
  • 有一定程度的编程经验,能快速上手。

1.1 为什么要写这个文档

1.2 参与贡献

如果你对本文档有任何意见或建议,欢迎提交issue或者pull request。共同完善这份文档,也是对开源事业的贡献。

欢迎大家跟随作者微信公众号java-liberation,一起交流、分享Java技术。

2.K. Java 8 Stream API 中文文档翻译版
2.1 Stream概述
Stream 是 Java 8 引入的一个新的编程模型。主要用于声明式地对数据流做各种操作,比如 filter(), map() 和 reduce() 操作。流可以无限期地执行,并且不会存储元素。Streams 提供了一种高效且易用的方法来处理数据,但和传统的集合类有些不同。首先,Stream 没有存储数据的特点,所有操作都是懒惰的,只有在调用 terminal operation 时,才会真正执行操作,并且会生成结果。其次,流只能迭代一次,而集合则可以迭代任意次数。第三,流操作是延迟执行的,这就意味着不会立即执行,只有等到需要的时候才会执行。

从这个角度看,流可以视为一种特殊的集合视图,集合视图支持普通集合类的大多数操作,但可以有一些限制。例如,集合视图只能遍历一次,流却可以无限期迭代。而且,集合视图不保存数据,只能进行计算操作,而流则可以操作原始数据流。

使用流的一般步骤如下:

  1. 创建数据源,例如 Collection 或 Arrays。
  2. 通过Stream工厂方法创建流。
  3. 通过中间操作来处理流,形成一条流水线。
  4. 执行终止操作,产生结果。

这一流程和集合类类似,只不过集合类一般由集合元素组成,而流则是在数据源上的管道。

2.2 数据源
Java 8 中的流可以从各种数据源创建,包括 Collection,Arrays,I/O 流和并发数据结构。下面是一些常用的方法:

  • Collection.stream() 方法,用于从 Collection 创建流;
  • Arrays.stream() 方法,用于从数组创建一个顺序流;
  • Files.lines(Path path) 方法,用于从文件读取文本行;
  • ExecutorService.submit() 方法,用于异步执行任务,并创建 CompletableFuture 对象。

2.2.1 文件流

下面是一个示例,演示了如何从文件中读取文本行,并输出到控制台。

try (Stream lines = Files.lines(Paths.get(“data.txt”))) {
lines.forEach(System.out::println);
}

 上面的例子使用了 try-with-resources 语句来自动关闭资源。Files.lines() 方法返回的是 Stream<String> 对象,可以使用 forEach() 方法输出到控制台。

 注意,使用这种方式打开的文件流,并不需要手动关闭,Java 8 会自己管理。另外,也可以采用 BufferedReader 来读取文件内容。
try (BufferedReader reader = new BufferedReader(new FileReader("data.txt"));
    Stream<String> lines = reader.lines()) {
    
    
    lines.forEach(System.out::println);
} catch (IOException e) {
    
    
    e.printStackTrace();
}

2.2.2 并发集合

Java 8 还添加了对并发集合的支持,包括 ConcurrentHashMap,ConcurrentLinkedQueue,BlockingQueue,和 CopyOnWriteArrayList。并发集合通过为并发访问提供同步机制,使多个线程可以安全地访问这些集合,而不需要加锁。

2.2.2.1 ConcurrentHashMap

ConcurrentHashMap 支持高并发访问,所以可以在多线程环境下使用。它的 key-value 对是线程安全的,可以通过 putIfAbsent() 方法避免重复插入。除此之外,ConcurrentHashMap 支持原子操作,允许多个更新操作并发执行,并保证正确的结果。

// create a ConcurrentHashMap with initial capacity of 10 and load factor of 0.75f
Map<Integer, String> map = new ConcurrentHashMap<>(10, 0.75f);

// use putIfAbsent method to avoid duplicates
for (int i = 1; i <= 10; i++) {
    
    
    map.putIfAbsent(i, "Value" + i);
}

// print the size of the map
System.out.println("Size: " + map.size());

上面的例子展示了如何使用 ConcurrentHashMap 避免重复插入。

2.2.2.2 ConcurrentLinkedQueue

ConcurrentLinkedQueue 是一个线程安全的队列,可以通过 add() 方法在队尾添加元素,peek() 方法获取队首元素,isEmpty() 方法检查队列是否为空,remove() 方法移除指定元素。

Queue<String> queue = new ConcurrentLinkedQueue<>();

// enqueue elements into the queue
queue.add("apple");
queue.add("banana");
queue.add("orange");

// peek at the first element in the queue
String firstElement = queue.peek();
System.out.println("First Element: " + firstElement);

// check if the queue is empty
boolean isEmpty = queue.isEmpty();
System.out.println("Is Empty: " + isEmpty);

// dequeue an element from the front of the queue
queue.remove();
System.out.println("Dequeued Element: " + queue.peek());

上面的例子展示了 ConcurrentLinkedQueue 的基本操作。

2.2.2.3 BlockingQueue

BlockingQueue 是一个接口,表示了一个先入先出的容器,可以通过 put() 方法添加元素,take() 方法获取元素,peek() 方法查看队首元素,isEmpty() 方法检查队列是否为空,offer() 方法尝试放入元素,poll() 方法尝试获取元素。除此之外,BlockingQueue 提供了其他阻塞方法,例如 put() 和 take() 方法都会阻塞当前线程,直到有空间或可用元素。

// create a bounded blocking queue with maximum size of 10
BlockingQueue<String> queue = new LinkedBlockingDeque<>(10);

// enqueue elements into the queue
queue.offer("apple");
queue.offer("banana");
queue.offer("orange");

// peek at the first element in the queue
String firstElement = queue.peek();
System.out.println("First Element: " + firstElement);

// check if the queue is full or empty
boolean isFull = queue.remainingCapacity() == 0;
System.out.println("Is Full: " + isFull);

// dequeue an element from the front of the queue
String dequeuedElement = queue.poll();
System.out.println("Dequeued Element: " + dequeuedElement);

上面的例子展示了 BlockingQueue 的基本操作。

2.2.2.4 CopyOnWriteArrayList

CopyOnWriteArrayList 是 Java 8 新增的一个线程安全的列表实现。它通过在修改时复制整个底层数组来实现一致性,所以读操作不会因竞争造成阻塞。与 ArrayList 相比,CopyOnWriteArrayList 具有很好的写性能,但是读性能略差。

List<String> list = new CopyOnWriteArrayList<>();

// add elements to the list
list.add("apple");
list.add("banana");
list.add("orange");

// iterate over the elements using streams
int count = list.stream().filter(s -> s.startsWith("b")).count();
System.out.println("Count of items starting with 'b': " + count);

上面的例子展示了 CopyOnWriteArrayList 的基本操作。

2.2.3 数据转换

2.2.3.1 筛选过滤
Stream API 提供了许多便捷的方法用来过滤数据,例如 filter() 方法用来选择满足条件的元素,limit() 方法用来截取流的部分元素,distinct() 方法用来消除重复元素,skip() 方法用来跳过前 n 个元素。下面是一个示例,演示了如何根据长度过滤字符串:

List<String> strings = Arrays.asList("foo", "", "bar", null, "baz", "qux", "");
strings.stream()
       .filter(Objects::nonNull)      // remove null values
       .filter(str ->!str.isEmpty()) // remove empty strings
       .filter(str -> str.length() > 2) // keep only longer than two characters
       .forEach(System.out::println);    // output filtered elements

上面的例子使用 filter() 方法对 strings 列表中的元素进行过滤,首先删除 null 值,然后保留非空字符串,最后只保留长度大于等于三个的字符串。forEach() 方法用来输出过滤后的元素。

2.2.3.2 映射
Stream API 提供了 map() 方法来对元素进行转换,例如,可以把每个元素转换成大写字母,或者数字的平方根。下面是一个示例,演示了如何把字符串转化为对应的整数:

List<String> strings = Arrays.asList("one", "two", "three", "four", "five");
strings.stream()
       .mapToInt(Integer::parseInt)   // convert string to integer
       .average()                     // calculate average value
       .ifPresent(avg -> System.out.println("Average: " + avg));     // output result

上面的例子使用 mapToInt() 方法对 strings 列表中的元素进行转换,使用 Integer.parseInt() 将每个元素转换成整数,然后求平均值。

2.2.3.3 分组
Stream API 提供了分组操作 groupBy() 和 join() ,用于对元素进行分组。groupBy() 方法接受分类器函数作为参数,并按该函数的结果对元素进行分组。join() 方法接受 delimiter 参数,用于连接分组得到的字符串。下面是一个示例,演示了如何将字符串按照长度分组:

List<String> strings = Arrays.asList("abcde", "bcdefg", "cdefghij", "defghi", "efgijkl");
Map<Integer, List<String>> groups =
    strings.stream()
           .collect(Collectors.groupingBy(str -> str.length()));
groups.forEach((len, words) -> System.out.printf("%d: %s%n", len, words));

上面的例子使用 groupingBy() 方法对 strings 列表中的元素进行分组,将相同长度的字符串归为一组。输出的结果显示每组元素的数量。

2.2.3.4 合并
Stream API 提供了 concat() 方法用于合并两个流,flatMap() 方法用于flatMap操作,即将流中的元素转化成另一个流,然后再进行合并。下面是一个示例,演示了如何合并两个字符串流:

Stream<String> stream1 = Stream.of("a", "b", "c");
Stream<String> stream2 = Stream.of("d", "e", "f");
Stream.concat(stream1, stream2).forEach(System.out::println);

上面的例子使用 concat() 方法合并了 stream1 和 stream2,并打印结果。

Stream<List<Integer>> streamOfLists = Stream.of(
    Arrays.asList(1, 2),
    Arrays.asList(3, 4, 5),
    Arrays.asList(6, 7)
);

streamOfLists.flatMap(List::stream)
            .forEach(System.out::println);

上面的例子使用 flatMap() 方法,将流中的 List 转化成单个元素的流,然后输出结果。

2.2.3.5 数学运算
Stream API 提供了 reduce() 方法用于对元素进行汇总,例如求和,最大值,最小值等。下面是一个示例,演示了如何计算字符串长度的总和:

List<String> strings = Arrays.asList("hello", "world", "!");
int sum = strings.stream()
               .mapToInt(String::length)
               .sum();
System.out.println("Total length: " + sum);

上面的例子使用 mapToInt() 方法将字符串的长度映射成整数,然后求和得到字符串的总长度。

2.2.3.6 查找元素
Stream API 提供了 findFirst() 方法查找第一个匹配元素,orElseGet() 方法用于返回默认值。下面是一个示例,演示了如何查找数组中的最大值:

int[] numbers = {
    
    5, 3, 10, 7, 2};
OptionalInt max = Arrays.stream(numbers)
                     .max();
int maxValue = max.orElse(-1);
System.out.println("Max Value: " + maxValue);

上面的例子使用 max() 方法找到数组中的最大值,并用 orElse() 方法设置默认值 -1 。

2.2.3.7 排序
Stream API 提供了 sorted() 方法对元素进行排序,例如根据字符串的长度进行排序,或者根据数字大小排序。下面是一个示例,演示了如何排序字符串:

List<String> strings = Arrays.asList("ccc", "aaaa", "bb", "ddddd");
strings.stream()
       .sorted()                 // sort by natural order
       .forEach(System.out::println);

strings.stream()
       .sorted(Comparator.reverseOrder())// reverse order
       .forEach(System.out::println);

上面的例子使用 sorted() 方法对 strings 列表进行排序,默认为自然排序,输出结果为 ccc, bb, ddddd, aaaa。第二种情况为降序排列,输出结果为 ddddd, ccc, bb, aaaa。

2.2.3.8 约减
Stream API 提供了 limit() 方法用来截取流的部分元素,dropWhile() 方法用于去掉流中的元素,直到指定的条件不满足。下面是一个示例,演示了如何从集合中删除前两个元素:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
Iterator<Integer> iterator = numbers.iterator();
while (iterator.hasNext() && iterator.hasNext()) {
    
    
    iterator.next();
    iterator.next();
}
List<Integer> sublist = Lists.newArrayList(iterator);

numbers.removeAll(sublist);
System.out.println(numbers);

上面的例子使用迭代器的方式删除前两个元素,然后使用 removeAll() 方法将它们从列表中移除。

2.2.3.9 收集结果
Stream API 提供了 collect() 方法用来收集流中的元素,例如将流转换成集合、数组或者字符串。下面是一个示例,演示了如何将流转换成集合:

Stream<String> words = Stream.of("Hello", "World");
Set<String> set = words.collect(Collectors.toSet());
System.out.println(set);

上面的例子使用 toSet() 方法将 words 流转换成集合,输出结果为 [World, Hello] 。

2.2.3.10 执行多个操作
Stream API 支持多个操作,可以顺序地、逐个地、并行地执行。下面是一个示例,演示了如何并行地执行多个操作:

long start = System.nanoTime();
IntStream.rangeClosed(1, 1_000_000)
        .parallel()          // enable parallel processing
        .filter(num -> num % 2 == 0)
        .distinct()
        .limit(100)           // retrieve only top 100 results
        .forEach(System.out::println);
long end = System.nanoTime();

double seconds = (end - start) / 1_000_000_000.0;
System.out.format("Execution time: %.3fs%n", seconds);

上面的例子使用 rangeClosed() 方法生成 1 到 1000000 之间的整数流,并启用并行处理。filter() 方法筛选奇数,distinct() 方法消除重复项,limit() 方法仅保留最前面的 100 个结果,forEach() 方法输出结果。打印执行时间。

猜你喜欢

转载自blog.csdn.net/universsky2015/article/details/132033769