Java函数式编程中的流和集合处理函数
Java 8引进了函数式编程的特性,在此之下,就有一个很重要的概念 - 流。流(Stream)可以看做是一组元素的集合,但是它却具有和传统集合完全不同的操作方式,比如:
- 不支持使用下标索引来获取元素
- 支持操作链(intermediate operation)和终止操作(Terminal operation)
同时,Java 8还引入了一整套全新的流式操作函数,它们可以让我们更加方便、高效地对流进行操作。本文将详细介绍Java 8中的流和集合处理函数。
### 流
在Java 8中,流是一个数据元素序列,在执行超过单个元素的操纵(filter,map等)时,它可以提供更高效的平行执行。
Java 8中,流的特征如下:
- 不存储值的集合。流不会类似于数组或列表那样存储值。它们仅仅是某个源(Collections、Arrays、I/O channel、产生器generator)在中间执行操作过程中输送的值的管道。
- 不改变源对象的情况下进行操作。流操作不会改变源对象。例如,它不会改变列表。而中间操作链中的每个操作都会返回一个新的流。
下面,让我们通过几个例子来看一下Java 8中的流操作。
#### 筛选操作
每一个 Stream 都接受一个指定的输入源。例如, Collection 提供了两个方法 stream() 与 parallelStream() 来获取一个流,并且类型为 Stream<T>,常用的方法有:
- filter():过滤数据
- distinct():去重
- limit():限制数量
- skip():跳过数据
使用流的代码示例:
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 5, 6, 7, 8, 9); List<Integer> evenNumbers = numbers.stream().filter(n -> n % 2 == 0).collect(Collectors.toList()); List<Integer> distinctNumbers = numbers.stream().distinct().collect(Collectors.toList()); List<Integer> limitedNumbers = numbers.stream().limit(5).collect(Collectors.toList()); List<Integer> skippedNumbers = numbers.stream().skip(5).collect(Collectors.toList());
#### 映射操作
映射为每一个元素 *x* 建立新元素 *f(x)*,其中 *f* 为一个方法,它把输入值转换成另外一个值。常见的方法有:
- map():映射数据
- flatmap():多个流合并成一个流
使用流的代码示例:
List<String> names = Arrays.asList("apple", "banana", "orange");
List<Integer> nameLengths = names.stream().map(String::length).collect(Collectors.toList());
List<String> words = Arrays.asList("hello", "world");
List<String> charList = words.stream().flatMap(word -> Arrays.stream(word.split(""))).collect(Collectors.toList());
#### 合并操作
常用的合并流的方法有:
- concat():合并两个流为一个流
- distinct():流去重,去掉重复的元素
- peek():观察元素,并在元素上执行后续操作
- forEach():遍历元素
使用流的代码示例:
List<Integer> firstNumbers = Arrays.asList(1, 2, 3); List<Integer> secondNumbers = Arrays.asList(4, 5, 6); List<Integer> combinedNumbers = Stream.concat(firstNumbers.stream(), secondNumbers.stream()).collect(Collectors.toList()); List<Integer> distinctNumbers = combinedNumbers.stream().distinct().collect(Collectors.toList()); distinctNumbers.stream().peek(System.out::println).forEach(System.out::println);
### 集合处理函数
Java 8中,提供了一套全新的API,能够更高效、更简洁地处理集合中的元素,它们是:
所有集合函数我们都可以这样分类:
- Predicate(断言)
- Function
- Consumer
- UnaryOperator/ BinaryOperator
#### Predicate(断言)
Predicate接口是Java 8中新增的一个函数式接口,它的作用就是传递一个参数,返回一个布尔值。常用的方法有:
- test():对参数进行判断
使用Predicate实现对集合的筛选,代码示例:
List<String> names = Arrays.asList("Sami", "Gavin", "Frank", "Helen", "Zoey");
Predicate<String> startsWithS = s -> s.startsWith("S");
Predicate<String> longerThan5 = s -> s.length() > 5;
List<String> filteredNames = names.stream().filter(startsWithS.and(longerThan5)).collect(Collectors.toList());
#### Function
Function 接口表示一个函数,它接受一个参数并返回一个结果,常用的方法有:
- apply():对输入参数进行处理后返回结果
使用Function实现对集合元素的转换,代码示例:
List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9); Function<Integer, String> intToString = Object::toString; List<String> stringNumbers = numbers.stream().map(intToString).collect(Collectors.toList());
#### Consumer
Consumer 接口表示执行特定操作的对象。它接受一个参数,但没有返回值,常用的方法有:
- accept():对输入参数进行处理后不返回结果
使用Consumer实现对集合元素的遍历,代码示例:
List<String> names = Arrays.asList("Sami", "Gavin", "Frank", "Helen", "Zoey");
Consumer<String> printName = name -> System.out.println(name);
names.forEach(printName);
#### UnaryOperator/BinaryOperator
UnaryOperator接口是一种特殊类型的Function,它的输入和输出皆为同一类型。常用的方法有:
- apply():对输入参数进行处理后返回结果
BinaryOperator接口同样是一种特殊类型的Function,它的输入和输出皆为同一类型。但是需要注意,长度为2;并且输入和输出类型相同,组成了一个操作数,常用的方法有:
- apply():对输入参数进行处理后返回结果
使用UnaryOperator和BinaryOperator实现实现集合元素的修改或计算,代码示例:
List<Integer> numbers = Arrays.asList(1,2,3,4,5); UnaryOperator<Integer> square = n -> n * n; numbers.replaceAll(square); BinaryOperator<Integer> add = (n1, n2) -> n1 + n2; int sum = numbers.stream().reduce(0, add);
### 总结
Java 8中流和集合处理函数为我们提供了更加高效、简洁的编程方式,我们可以通过流的处理函数对集合元素进行筛选、映射等处理,同时,我们也可以使用集合处理函数对集合元素进行修改和计算等操作。当然,Java 8中还提供了很多其他的函数式编程相关的特性,有兴趣的朋友不妨继续深入学习。
