在Stream流中添加中间操作

在Stream流中添加中间操作

将一个Stream流映射到另一个Stream流

使用map()方法将一个stream映射为另一个stream,参数为Function。使用Function对stream中的元素进行处理。

映射可以改变元素类型,也可以不改变元素类型

List<String> strings = List.of("one", "two", "three", "four");
Function<String, Integer> toLength = String::length;
Stream<Integer> ints = strings.stream()
                              .map(toLength);

由于没有添加终端操作,这段代码不做任何事情,并不处理任何数据。

添加一个终端操作collect(Collectors.toList())处理数据到一个列表中:

List<String> strings = List.of("one", "two", "three", "four");
List<Integer> lengths = strings.stream()
                               .map(String::length)
                               .collect(Collectors.toList());
System.out.println("lengths = " + lengths);

运行次代码输出:

lengths = [3, 3, 5, 4]

有三种方法可以从 Stream 转到原始类型的流:mapToInt()mapToLong()mapToDouble()

List<String> strings = List.of("one", "two", "three", "four");
IntSummaryStatistics stats = strings.stream()
                                    .mapToInt(String::length)
                                    .summaryStatistics();
System.out.println("stats = " + stats);

输出结果:

stats = IntSummaryStatistics{count=4, sum=15, min=3, average=3,750000, max=5}

过滤stream流

过滤就是通过predicate谓词(断言)丢弃一些stream流中的元素。过滤可用于stream流中的对象或原始数据类型。

假设你需要计算长度为3的字符串的个数

List<String> strings = List.of("one", "two", "three", "four");
long count = strings.stream()
                    .map(String::length)
                    .filter(length -> length == 3)
                    .count();
System.out.println("count = " + count);

输出结果

count = 2

注意count()为终端操作,计算出流中已处理的元素个数。

flatMap()用来处理一对多的关系

public class City {
    
    private String name;
    private int population;

    // constructors, getters
    // toString, equals and hashCode
}

public class State {
    
    private String name;
    private List<City> cities;

    // constructors, getters
    // toString, equals and hashCode
}

计算所有城市的人口

List<State> states = ...;

int totalPopulation = 0;
for (State state: states) {
    for (City city: state.getCities()) {
        totalPopulation += city.getPopulation();
    }
}

System.out.println("Total population = " + totalPopulation);

内部循环代码是映射-归约形式

List<State> states = ...;

int totalPopulation = 0;
for (State state: states) {
    totalPopulation += state.getCities().stream().mapToInt(City::getPopulation).sum();
}

System.out.println("Total population = " + totalPopulation);

flatmap操作打开对象之间一对多的关系,并创建stream流。flatMap()方法接收一个特殊的参数Function<? super T,? extends Stream<? extends R>>返回Stream对象。类之间的关系由该Function定义。

List<State> states = ...;

int totalPopulation = 
        states.stream()
              .flatMap(state -> state.getCities().stream())
              .mapToInt(City::getPopulation)
              .sum();

System.out.println("Total population = " + totalPopulation);

flatMap()分2步处理stream流:

  1. 通过Function映射stream流中的所有元素,从Stream<State>创建Stream<Stream<City>>
  2. flattening(展平,压扁)Stream<Stream<City>>使其变为单个Stream<City>

使用flatMap()mapMulti()(jdk16以上)验证元素转换

flatMap()可以验证stream流中元素的转换。

假设你有一个字符串流,字符串代表数字。你需要使用中间操作Integer.parseInt()转换。但是如果字符串为空字符串、null、有额外的空格等,解析会抛出NumberFormatException异常。当然你可以先过滤掉异常等字符串。

使用过滤并不是一个很好的方法

Predicate<String> isANumber = s -> {
    try {
        int i = Integer.parseInt(s);
        return true;
    } catch (NumberFormatException e) {
        return false;
    }
};

一个的缺陷是首先你需要实际的过滤操作,然后在使用同样的函数进行中间映射操作。另一个缺陷是在catch代码块中返回并不是一个好的编码习惯。

正确的做法应该是可以解析的直接返回,不能解析的什么都不返回。可以使用flatMap(),可以解析的返回一个解析结果stream流,不能解析的返回一个空的stream流。

Function<String, Stream<Integer>> flatParser = s -> {
    try {
        return Stream.of(Integer.parseInt(s));
    } catch (NumberFormatException e) {
    }
    return Stream.empty();
};

List<String> strings = List.of("1", " ", "2", "3 ", "", "3");
List<Integer> ints = 
    strings.stream()
           .flatMap(flatParser)
           .collect(Collectors.toList());
System.out.println("ints = " + ints);

运行结果

ints = [1, 2, 3]

使用flatMap()可以很好的工作,但也有一定的开销:需要为stream流中的每一个元素创建stream流。jdk16添加了mapMulti()Stream API。mapMulti()需要BiConsumer参数。

BiConsumer有2个参数:

  • 需要映射的stream流中的元素
  • BiConsumer 需要使用映射结果调用的 Consumer

调用consumer将元素添加到结果stream流中。如果不调用consumer则元素不会被添加到stream流中。

List<Integer> ints =
        strings.stream()
               .<Integer>mapMulti((string, consumer) -> {
                    try {
                        consumer.accept(Integer.parseInt(string));
                    } catch (NumberFormatException ignored) {
                    }
               })
               .collect(Collectors.toList());
System.out.println("ints = " + ints);

运行结果,但不会添加额外的stream流了

ints = [1, 2, 3]

删除重复项并对流进行排序

distinct()使用元素的hashCode()equals()去重。

sorted()使用元素的Comparator或提供Comparator进行排序。

distinct()可以用于未绑定(无限)stream流,但sorted()不行。

限制和跳过stream流的元素

Stream API 为您提供了两种选择流元素的方法:

  1. 基于索引
  2. Predicate谓词(断言)

skip()limit()基于索引,它们都需要一个long类型参数。

List<Integer> ints = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9);

List<Integer> result = 
    ints.stream()
        .skip(2)
        .limit(5)
        .collect(Collectors.toList());

System.out.println("result = " + result);

结果

result = [3, 4, 5, 6, 7]

jdk9添加了2个方法代替基于索引的skip()limit(),新方法基于Predicate谓词(断言)。

  • dropWhile(predicate)丢弃stream流中的元素直到Predicate返回true
  • takeWhile(predicate)恰恰相反:它将元素传输到下一个流,直到Predicate返回false

这2个方法像门一样工作,dropWhile()打开门,不再关闭。takeWhile()关闭门,不在打开。

连接stream流

Stream API 提供了几种模式来将多个流连接成一个。最常使用的是concat(),该方法只能连接2个stream流,如果要连接2个以上的stream流,建议使用flatMap()

List<Integer> list0 = List.of(1, 2, 3);
List<Integer> list1 = List.of(4, 5, 6);
List<Integer> list2 = List.of(7, 8, 9);

// 1st pattern: concat
List<Integer> concat = 
    Stream.concat(list0.stream(), list1.stream())
          .collect(Collectors.toList());

// 2nd pattern: flatMap
List<Integer> flatMap =
    Stream.of(list0.stream(), list1.stream(), list2.stream())
          .flatMap(Function.identity())
          .collect(Collectors.toList());

System.out.println("concat  = " + concat);
System.out.println("flatMap = " + flatMap);

结果

concat  = [1, 2, 3, 4, 5, 6]
flatMap = [1, 2, 3, 4, 5, 6, 7, 8, 9]

使用 flatMap() 方式更好的原因是 concat() 在连接期间会创建中间流。当您使用 Stream.concat() 时,会创建一个新流来连接您的两个流。

使用flatMap()模式,您只需创建一个Stream<Stream<T>>流来保存所有stream流并执行flatMap()。开销要低得多。

concat()产生SIZEDstream流,而flatMap()不行。

调试stream流

peek()

List<String> strings = List.of("one", "two", "three", "four");
List<String> result =
        strings.stream()
                .peek(s -> System.out.println("Starting with = " + s))
                .filter(s -> s.startsWith("t"))
                .peek(s -> System.out.println("Filtered = " + s))
                .map(String::toUpperCase)
                .peek(s -> System.out.println("Mapped = " + s))
                .collect(Collectors.toList());
System.out.println("result = " + result);

结果

Starting with = one
Starting with = two
Filtered = two
Mapped = TWO
Starting with = three
Filtered = three
Mapped = THREE
Starting with = four
result = [TWO, THREE]
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值