Java 函数式编程 - Stream API Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。 Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。

Java 函数式编程 - Stream API

Stream 介绍

Java 8 API 添加了一个新的抽象称为流 Stream,可以让你以一种声明的方式处理数据。
Stream 使用一种类似用 SQL 语句从数据库查询数据的直观方式来提供一种对 Java 集合运算和表达的高阶抽象。
Stream API 可以极大提高 Java 程序员的生产力,让程序员写出高效率、干净、简洁的代码。

这种风格将要处理的元素集合看作一种,流在管道中传输,并且可以在管道的节点上进行处理,比如筛选,排序,聚合等。
元素流在管道中经过中间操作的处理,最后由最终操作得到前面处理的结果。

Stream 的特性

  1. Stream 不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
  2. Stream 不会改变数据源,通常情况下会产生一个新的集合或一个值。
  3. Stream 具有延迟执行特性,只有调用终止操作时,中间操作才会执行。

Stream 操作分类

中间操作:惰性求值,只是对操作进行了记录,并不会立即执行,需要等到执行终止操作的时候才会进行实际的计算

终止操作:执行计算操作,返回计算结果,每个流只能执行一次终止操作

无状态:指元素的处理不受之前元素的影响

有状态:指该操作只有拿到所有元素之后才能继续下去

非短路操作:指必须处理所有元素才能得到最终结果

短路操作:指遇到某些符合条件的元素就可以得到最终结果,如 A || B,只要 A 为 true,则无需判断 B 的结果

中间操作

无状态

unordered(), filter(), map(), mapToXXX(), flatMap(), flatMapToXXX(), peek()

有状态

distanct(), sorted(), limit(), skip()

终止操作

非短路操作

forEach(), forEachOrdered(), toArray(), collect(), max(), min(), count()

短路操作

anyMatch(), allMatch(), noneMatch(), findFirst(), findAny()

串行流与并行流

Stream API 可以声明性地通过 parallel()sequential() 在并行流和串行流(顺序流)之间进行切换。

串行流:通过单线程的方式来处理数据的流
并行流:把一个内容分成多个数据块,并用不同的线程分别处理每个数据块的流,最后把不同线程的处理结果进行汇总,是基于 Fork/Join 框架实现的

并行流使用 ForkJoinPool.commonPool 线程池,默认的线程数量就是你的处理器核心数。多个并行流之间默认使用的是同一个线程池,所以 IO 操作尽量不要放进并行流中,否则会阻塞其他并行流。

在单次计算量较少的情况下并行操作并没有比串行操作快,因为对数据进行分片和汇总等操作也需要消耗时间。

流的创建方式

  1. 使用 Collection 下的 stream()parallelStream() 方法
List<String> list = new ArrayList<>();
Stream<String> stream = list.stream();
Stream<String> parallelStream = list.parallelStream();
  1. 使用 Arrays 的 stream() 方法,将数组转成流
Integer[] nums = new Integer[10];
Stream<Integer> stream = Arrays.stream(nums);
  1. 使用 Stream 的静态方法 of()iterate()generate()concat()
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);  
  
// 无限长的偶数流  
Stream<Integer> stream2 = Stream.iterate(0, x -> x + 2);  
stream2.forEach(System.out::println); // 0 2 4 6 8 10 ...  
  
// 无限长的随机数浮点数流  
Stream<Double> stream3 = Stream.generate(Math::random);  
stream3.forEach(System.out::println);

// 连接两个流
Stream.concat(stream, stream2);
  1. 使用 BufferedReader.lines() 方法,将每行内容转成流
BufferedReader reader = new BufferedReader(new FileReader("F:\\test_stream.txt"));
Stream<String> lineStream = reader.lines();
  1. 使用 Pattern 的 splitAsStream() 方法,将字符串分割成流
Pattern pattern = Pattern.compile(",");
Stream<String> stream = pattern.splitAsStream("a,b,c");

流的中间操作

筛选与切片

filter:过滤掉流中的某些元素

// 过滤掉流中的奇数
Stream.of(1, 2, 3, 4)
    .filter(i -> i % 2 == 0)
    .forEach(System.out::println); 
// 输出:2 4

limit(n):只获取前 n 个元素

// 只获取流的中前2个元素
Stream.of(1, 2, 3, 4)
    .limit(2)
    .forEach(System.out::println);
// 输出:1 2

skip(n):跳过 n 个元素,配合 limit(n) 可实现分页

// 跳过流中的前2个元素
Stream.of(1, 2, 3, 4)
    .skip(2)
    .forEach(System.out::println);
// 输出:3 4

distinct:去除流中的重复元素,通过元素的 equals()hashCode() 方法来去重

Stream.of(1, 2, 3, 4, 3 ,2)
    .distinct()
    .forEach(System.out::println);
// 输出:1 2 3 4

映射

map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素

// 将流中的每个整数乘以2
Stream.of(1, 2, 3, 4)
    .map(i -> i * 2)
    .forEach(System.out::println);
// 输出:2 4 6 8

// 将流中的每个整数转换成浮点数
Stream.of(1, 2, 3, 4)
    .map(i -> (double) i)
    .forEach(System.out::println);
// 输出:1.0 2.0 3.0 4.0

flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把这些流连接成一个流

// 将两个字符串按照逗号分割,并汇总分割后的元素
Stream.of("1,2,3", "a,b,c")
    .flatMap(str -> Arrays.stream(str.split(",")))
    .forEach(System.out::println);
// 输出:1 2 3 a b c

排序

sorted():自然排序(从小到大),流中元素需实现 Comparable 接口

// 从小到大排序
Stream.of(4, 2, 1, 3)
    .sorted()
    .forEach(System.out::println);
// 输出:1 2 3 4

sorted(Comparator):自定义排序,传入自定义的 Comparator 排序器

// 从大到小排序
Stream.of(4, 2, 1, 3)
    .sorted(Comparator.reverseOrder())
    .forEach(System.out::println);
// 输出:4 3 2 1

消费

peek:观察流中的每个元素

Stream.of(1, 2, 3, 4)
    .peek(i -> System.out.println(i))
    .forEach(System.out::println);
// 输出:1 1 2 2 3 3 4 4

流的终止操作

消费

forEach:对于串行流是顺序遍历流的元素;对于并发流,则是随机遍历
forEachOrdered:对于串行流,跟 forEach 一样;对于并发流,则是按照顺序遍历流的元素

匹配、聚合操作

allMatch:接收一个 Predicate 函数,当流中每个元素都符合该断言时才返回 true,否则返回 false

// 流中元素是否都是偶数
boolean result = Stream.of(0, 2, 4, 8)
    .allMatch(i -> i % 2 == 0);
System.out.println(result);
// 输出:true

noneMatch:接收一个 Predicate 函数,当流中每个元素都不符合该断言时才返回 true,否则返回 false

// 流中元素是否都大于0
boolean result = Stream.of(1, 2, 3, 4)
    .noneMatch(i -> i <= 0);
System.out.println(result);
// 输出:true

anyMatch:接收一个 Predicate 函数,只要流中有一个元素满足该断言则返回 true,否则返回 false

// 流中是否有任意一个元素小于0
boolean result = Stream.of(1, 2, -1, 3)
    .anyMatch(i -> i < 0);
System.out.println(result);
// 输出:true

findFirst:返回流中第一个元素

Optional<Integer> first = Stream.of(1, 2, 3, 4).findFirst();
System.out.println(first.get());
// 输出:1

findAny:返回流中的任意元素,对于串行流一般会返回第一个结果,对于并行流,那就不能确保是第一个。找到第一个元素在并行流上限制更多。如果你不关心返回的元素是哪个,请使用 findAny,因为它在使用并行流时限制较少;

Optional<Integer> first = Stream.of(1, 2, 3, 4).findAny();
System.out.println(first.get());
// 输出:1

count:返回流中元素的总个数

long count = Stream.of(1, 2, 3, 4).count();
System.out.println(count);
// 输出:4

max:返回流中元素最大值

Optional<Integer> max = Stream.of(1, 2, 3, 4)
    .max(Comparator.naturalOrder());
System.out.println(max.get());
// 输出:4

min:返回流中元素最小值

Optional<Integer> min = Stream.of(1, 2, 3, 4)
    .min(Comparator.naturalOrder());
System.out.println(min.get());
// 输出:1

归约操作

reduce

方法一
<U> U reduce(U identity, BiFunction<U, ? super T, U> accumulator, BinaryOperator<U> combiner);
参数
  • identity 初始值,如果流为空,则为此默认结果
  • accumulator 累加器,具有两个参数的函数,归约 reduce 运算后的部分结果和流的下一个元素
  • combiner 组合器,用于并行流合并 fork join 结果,串行流不起作用

注意reduce 方法对于串行流和并行流的逻辑是不一样

示例:

// 串行流:将流中元素相加
Integer result = Stream.of(1, 2, 3, 4)
    .reduce(100, (acc, i) -> {
        System.out.printf("acc: %d + %d = %d%n", acc, i, acc + i);
        return acc + i;
    }, (sum, part) -> {
        System.out.printf("com: %d + %d = %d%n", sum, part, sum + part);
        return sum + part;
    });
System.out.println(result);
// 输出:
// acc: 100 + 1 = 101
// acc: 101 + 2 = 103
// acc: 103 + 3 = 106
// acc: 106 + 4 = 110
// 110
// 并行流:将流中的每个元素都和identity相加,然后再把它们的结果加起来
Integer result = Stream.of(1, 2, 3, 4).parallel()
    .reduce(100, (acc, i) -> {
        System.out.printf("acc: %d + %d = %d%n", acc, i, acc + i);
        return acc + i;
    }, (sum, part) -> {
        System.out.printf("com: %d + %d = %d%n", sum, part, sum + part);
        return sum + part;
    });
System.out.println(result);
// 输出:
// acc: 100 + 3 = 103
// acc: 100 + 2 = 102
// acc: 100 + 4 = 104
// acc: 100 + 1 = 101
// com: 103 + 104 = 207
// com: 101 + 102 = 203
// com: 203 + 207 = 410
// 410
方法二
T reduce(T identity, BinaryOperator<T> accumulator);

示例:

// 将流中元素相加
Integer result = Stream.of(1, 2, 3, 4)
    .reduce(100, (acc, i) -> acc + i);
System.out.println(result);
// 输出:110
方法三
Optional<T> reduce(BinaryOperator<T> accumulator);

示例:

// 将流中元素相加
Optional<Integer> result = Stream.of(1, 2, 3, 4)
    .reduce((acc, i) -> acc + i);
System.out.println(result.get());
// 输出:10

收集操作

toArray

将流转换为数组

方法一
<A> A[] toArray(IntFunction<A[]> generator);
参数
  • generator 生成所需类型和长度的新数组的函数

示例:

Integer[] array = Stream.of(1, 2, 3, 4)
    .toArray(Integer[]::new);
System.out.println(Arrays.toString(array));
// 输出:[1, 2, 3, 4]
方法二
Object[] toArray();

示例:

Object[] array = Stream.of(1, 2, 3, 4)
    .toArray();
System.out.println(Arrays.toString(array));
// 输出:[1, 2, 3, 4]

collect

方法一
<R, A> R collect(Collector<? super T, A, R> collector);
参数
  • collector 收集器
// 将流中元素收集到list中
List<Integer> list = Stream.of(1, 2, 3, 4)
    .collect(Collectors.toList());
System.out.println(list);
// 输出 [1, 2, 3, 4]
方法二
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
参数
  • supplier 创建容器的函数
  • accumulator 累加器,用于将元素添加到容器中的函数
  • combiner 组合器,用于并行流合并 fork join 结果,串行流不起作用
// 将流中元素收集到list中
ArrayList<Integer> result = Stream.of(1, 2, 3, 4)
    .collect(
        () -> new ArrayList<>(),
        (list, item) -> list.add(item),
        (list1, list2) -> list1.addAll(list2));
System.out.println(result);
// 输出 [1, 2, 3, 4]

Collector 工具库:Collectors

以下示例中使用的数据:

class User {
    private Integer id;
    private String username;
    private Boolean sex;
    private Integer type;
    ...        
}

List<User> users = Arrays.asList(
        new User(1, "user1", true, 1),
        new User(2, "user2", false, 3),
        new User(3, "user3", true, 2),
        new User(4, "user2", false, 1));

归集(toList/toSet/toMap)

Collectors.toList()

List<Integer> list = users.stream().map(User::getId).collect(Collectors.toList());
System.out.println(list);
// 输出:[1, 2, 3, 4]

Collectors.toSet()

Set<String> set = users.stream().map(User::getUsername).collect(Collectors.toSet());
System.out.println(set);
// 输出:[user1, user2, user3]

Collectors.toMap()

方法一
<T, K, U, M extends Map<K, U>> Collector<T, ?, M> toMap(
    Function<? super T, ? extends K> keyMapper,
    Function<? super T, ? extends U> valueMapper,
    BinaryOperator<U> mergeFunction,
    Supplier<M> mapSupplier);
参数
  • keyMapper 把元素转成 key 的函数
  • valueMapper 把元素转成 value 的函数
  • mergeFunction 当 key 发生冲突时的合并函数
  • mapSupplier 创建 Map 容器的函数

示例:

// 创建用户ID到用户的映射,发生key冲突时取新的那个
HashMap<Integer, User> map = users.stream().collect(Collectors.toMap(
    User::getId,
    Function.identity(),
    (a, b) -> b,
    () -> new HashMap<>()));
System.out.println(map);
// 输出:{user1=User{id=1, ...}, user2=User{id=4, ...}, user3=User{id=3, ...}}
方法二

方法二调用了方法一

<T, K, U> Collector<T, ?, Map<K,U>> toMap(
    Function<? super T, ? extends K> keyMapper,  
    Function<? super T, ? extends U> valueMapper,  
    BinaryOperator<U> mergeFunction) {
    // 创建HashMap对象作为容器
    return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}

示例:

// 创建用户名到用户的映射,发生key冲突时取新的那个
Map<String, User> map = users.stream().collect(Collectors.toMap(
    User::getUsername, Function.identity(), (a, b) -> b));
System.out.println(map);
// 输出:{user1=User{id=1, ...}, user2=User{id=4, ...}, user3=User{id=3, ...}}
方法三

方法三调用了方法一

<T, K, U> Collector<T, ?, Map<K,U>> toMap(
    Function<? super T, ? extends K> keyMapper,
    Function<? super T, ? extends U> valueMapper) {
    // 发现重复元素时抛异常
    return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}

示例:

// 创建用户ID到用户的映射
Map<Integer, User> map = users.stream()
    .distinct()
    .collect(Collectors.toMap(User::getId, Function.identity()));
System.out.println(map);
// 输出:{1=User{id=1, ...}, 2=User{id=2, ...}, 3=User{id=3, ...}, 4=User{id=4, ...}}

统计(count/averaging)

Collectors 提供了一系列用于数据统计的静态方法:

计数:count 平均值:averagingIntaveragingLongaveragingDouble 最值:maxByminBy 求和:summingIntsummingLongsummingDouble 统计以上所有:summarizingIntsummarizingLongsummarizingDouble

分组(groupingBy/partitioningBy)

Collectors.groupingBy()

方法一
<T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(
    Function<? super T, ? extends K> classifier,
    Supplier<M> mapFactory,
    Collector<? super T, A, D> downstream);
参数
  • classifier 分组器,把元素转换成分组 key 的函数
  • mapFactory 创建 Map 容器的函数
  • downstream 分组后结果流的收集器

示例:

// 把用户列表按照类型分组,再按照性别进行分组
HashMap<Integer, Map<Boolean, List<User>>> map = users.stream()
    .collect(Collectors.groupingBy(
        User::getType,
        () -> new HashMap<>(),
        Collectors.groupingBy(User::getSex)));
printJson(map);

输出:

{
  "1" : {
    "false" : [ {"id" : 4, "username" : "user2", "sex" : false, "type" : 1} ],
    "true" : [ {"id" : 1, "username" : "user1", "sex" : true, "type" : 1} ]
  },
  "2" : {
    "true" : [ {"id" : 3, "username" : "user3", "sex" : true, "type" : 2} ]
  },
  "3" : {
    "false" : [ {"id" : 2, "username" : "user2", "sex" : false, "type" : 3} ]
  }
}
方法二

方法二调用了方法一

<T, K, A, D> Collector<T, ?, Map<K, D>> groupingBy(
    Function<? super T, ? extends K> classifier,
    Collector<? super T, A, D> downstream) {
    // 创建HashMap对象作为容器
    return groupingBy(classifier, HashMap::new, downstream);
}

示例:

// 把用户列表按照类型分组,然后再按照性别分组
Map<Integer, Map<Boolean, List<User>>> map = users.stream()
    .collect(Collectors.groupingBy(User::getType,
        Collectors.groupingBy(User::getSex)));
printJson(map);

输出:与方法一的示例一样

方法三

方法三调用了方法二

<T, K> Collector<T, ?, Map<K, List<T>>> groupingBy(
    Function<? super T, ? extends K> classifier) {
    // 把分组后的元素存到List中
    return groupingBy(classifier, toList());
}

示例:

// 把用户列表按照类型分组
Map<Integer, List<User>> map = users.stream()
    .collect(Collectors.groupingBy(User::getType));
System.out.println(map);

输出:

{
  "1" : [ {"id" : 1, ..., "type" : 1}, {"id" : 4, ..., "type" : 1} ],
  "2" : [ {"id" : 3, ..., "type" : 2} ],
  "3" : [ {"id" : 2, ..., "type" : 3} ]
}

Collectors.partitioningBy()

partitioningBy 方法相当于 groupingBy 的特例,只能把元素分为两个组 truefalse

方法一
<T, D, A> Collector<T, ?, Map<Boolean, D>> partitioningBy(
    Predicate<? super T> predicate,
    Collector<? super T, A, D> downstream);
参数
  • predicate 分区器,把元素转换成 true/false 的函数
  • downstream 分区后结果流的收集器

示例:

// 将用户列表按照性别分成两个区,然后再按照类型是否大于1分成两个区
Map<Boolean, Map<Boolean, List<User>>> map = users.stream()
    .collect(Collectors.partitioningBy(User::getSex,
        Collectors.partitioningBy(user -> user.getType() > 1)));
printJson(map);

输出:

{
  "false" : {
    "false" : [ {"id" : 4, "username" : "user2", "sex" : false, "type" : 1} ],
    "true" : [ {"id" : 2, "username" : "user2", "sex" : false, "type" : 3} ]
  },
  "true" : {
    "false" : [ {"id" : 1, "username" : "user1", "sex" : true, "type" : 1} ],
    "true" : [ {"id" : 3, "username" : "user3", "sex" : true, "type" : 2} ]
  }
}
方法二

方法二调用了方法一

<T> Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(
    Predicate<? super T> predicate) {
    // 把分组后的元素存到List中
    return partitioningBy(predicate, toList());
}

示例:

// 将用户列表按照性别分成两个区
Map<Boolean, List<User>> map = users.stream()
    .collect(Collectors.partitioningBy(User::getSex));
printJson(map);

输出:

{
  "false" : [ {"id" : 2, "sex" : false, ...}, {"id" : 4, "sex" : false, ...} ],
  "true" : [ {"id" : 1, "sex" : true, ...}, {"id" : 3, "sex" : true, ...} ]
}

字符串拼接(joining)

Collectors.joining()

方法一
Collector<CharSequence, ?, String> joining(
    CharSequence delimiter,
    CharSequence prefix,
    CharSequence suffix);
参数
  • delimiter 分隔符
  • prefix 前缀
  • suffix 后缀

示例:

String s = users.stream().map(User::getUsername)
    .collect(Collectors.joining(",", "(", ")"));
System.out.println(s);
// 输出:(user1,user2,user3,user2)
方法二

方法二调用了方法一

Collector<CharSequence, ?, String> joining(
    CharSequence delimiter) {
    // 前缀和后缀为空
    return joining(delimiter, "", "");
}

示例:

String s = users.stream().map(User::getUsername)
    .collect(Collectors.joining(","));
System.out.println(s);
// 输出:user1,user2,user3,user2
方法三
Collector<CharSequence, ?, String> joining()

示例:

String s = users.stream().map(User::getUsername)
    .collect(Collectors.joining());
System.out.println(s);
// 输出:user1user2user3user2

应用例子

例子一

将用户列表转换成 VO 列表

List<User> users = Arrays.asList(
    new User(1, "user1", true, 0),
    new User(2, "user2", false, 1),
    new User(3, "user3", true, 2),
    new User(4, "user4", true, 2)
);

{
    // 传统方式
    List<UserVO> userVOList = new ArrayList<>(users.size());
    for (User user : users) {
        userVOList.add(toUserVo(user));
    }
    System.out.println(userVOList);
}
{
    // Stream方式
    List<UserVO> userVOList = users.stream()
        .map(user -> toUserVo(user))
        .collect(Collectors.toList());
    System.out.println(userVOList);
}

例子二

将 User 列表转换成 id -> username 的映射

List<User> users = Arrays.asList(
    new User(1, "user1", true, 0),
    new User(2, "user2", true, 0),
    new User(3, "user3", true, 0),
    new User(4, "user4", true, 0)
);

{
    // 传统方式
    Map<Integer, String> map = new HashMap<>();
    for (User user : users) {
        map.put(user.getId(), user.getUsername());
    }
    System.out.println(map);
}
{
    // Stream方式
    Map<Integer, String> map = users.stream().collect(Collectors.toMap(
        User::getId, User::getUsername));
    System.out.println(map);
}

例子三

将用户列表的 username 按照在列表中的顺序用逗号拼接起来,并去重

List<User> users = Arrays.asList(
    new User(2, "user2", true, 0),
    new User(1, "user1", false, 1),
    new User(3, "user3", true, 2),
    new User(1, "user1", true, 2)
);

{
    // 传统方式
    StringBuilder usernames = new StringBuilder();
    Set<String> set = new HashSet<>();
    for (User user : users) {
        if (set.add(user.getUsername())) {
            usernames.append(user.getUsername()).append(',');
        }
    }
    if (usernames.length() != 0) {
        usernames.deleteCharAt(usernames.length() - 1);
    }
    System.out.println(usernames);
}
{
    // Stream方式
    String usernames = users.stream()
        .map(User::getUsername)
        .distinct()
        .collect(Collectors.joining(","));
    System.out.println(usernames);
}

参考