笔记

133 阅读5分钟

Java 8 Stream

什么是stream?

stream使用的是函数式编程模式,流表示包含着一系列元素的集合,我们可以对其做不同类型的操作,用来对这些元素执行计算。

  • Stream 不是集合 , 也不是数据结构 , 不可以保存数据

  • Stream 有点类似于高级 的 Iterator , 可以用于算法和计算

  • 不同于迭代器 , Stream 可以并行化操作 , 数据被分为很多段 , 在不同的线程中进行处理

  • 数据源、零个或多个中间操作 ( intermediate ) 以及零个或一个终端操作 ( terminal )

  • 所有中间操作都是惰性的 , 在管道开始工作之前,任何操作都不会产生任何效果

  • 终端操作有点像水龙头 , 开启了水龙头后 , 水才会流动 , 中间操作才会执行

    如:

List<String> myList =
    Arrays.asList("a1", "a2", "b1", "c2", "c1");
myList
    .stream() // 创建流
    .filter(s -> s.startsWith("c")) // 执行过滤,过滤出以 c 为前缀的字符串 ①start
    .map(String::toUpperCase) // 转换成大写
    .sorted() // 排序  ①end中间操作
    .forEach(System.out::println); // for 循环打印   ②终端操作
// C1
// C2

可以对流进行中间操作或者终端操作.

  • ①中间操作会再次返回一个流,所以,我们可以链接多个中间操作,注意这里是不用加分号的。如上filter 过滤,map 对象转换,sorted 排序,就属于中间操作。
  • ②终端操作是对流操作的一个结束动作,一般返回 void 或者一个非流的结果。如上 forEach循环 就是一个终止操作。

Java8 Stream 流是不能被复用的,一旦你调用任何终端操作,流就会关闭:

Stream<String> stream =
    Stream.of("d2", "a2", "b1", "b3", "c")
        .filter(s -> s.startsWith("a"));
stream.anyMatch(s -> true);    // ok  
stream.noneMatch(s -> true);   // exception   

当我们对 stream 调用了 anyMatch 终端操作以后,流即关闭了,再调用 noneMatch 就会抛出异常:

Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
	at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:229)
	at java.util.stream.ReferencePipeline.noneMatch(ReferencePipeline.java:459)
	at test.demo.main(demo.java:22)

为了克服这个限制,我们必须为我们想要执行的每个终端操作创建一个新的流链,例如,通过 Supplier 来包装一下流,通过 get() 方法来构建一个新的 Stream 流,如下所示:

Supplier<Stream<String>> streamSupplier =
    () -> Stream.of("d2", "a2", "b1", "b3", "c")
            .filter(s -> s.startsWith("a"));
streamSupplier.get().anyMatch(s -> true);   // ok true  流中任一元素满足 s.startsWith("a")
streamSupplier.get().noneMatch(s -> true);  // ok false  流中所有元素都不满足 s.startsWith("a")

通过构造一个新的流,来避开流不能被复用的限制, 这也是取巧的一种方式。

函数式接口 Supplier

java.util.function.Supplier 接口仅包含一个无参的方法: T get() 。用来获取一个泛型参数指定类型的对象数据。

package java.util.function;

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

高级操作

Streams 支持的操作很丰富,除了上面介绍的这些比较常用的中间操作,还有一些更复杂的操作,如

  • forEach 迭代流中的每个数据
  • map 映射每个元素对应的结果
  • filter 设置条件过滤元素
  • limit 获取指定数量的流
  • sorted 对流进行排序(降序.sorted(Comparator.reverseOrder()))
  • parallelStream 并行流
  • Collectors 实现归约操作

例如:

forEach | map | filter | sorted | limit

List<String> myList =
                Arrays.asList("a1", "a2", "b1", "c2", "c1","Cc","cd");
        myList.stream() // 创建流
                .filter(s -> s.startsWith("c")) // 执行过滤,过滤出以 c 为前缀的字符串
                .map(String::toUpperCase) // 转换成大写
                .sorted()// 排序
                .limit(3)//截取前3个
                .forEach(System.out::println);//for 循环打印

                //C1
                //C2
                //CD

运行结果

Collectors

  • 将流转换成集合和聚合元素,Collectors用于返回列表或字符串;.collect(Collectors.toList())
  • 合并字符串将“,”去除。.collect(Collectors.joining(","))

统计

  • 主要int double long 类型获取最大值.getMax(),最小数.getMin(),所有数和.getSum(),平均数.getAverage()

groupingBy

  • 分组求和,例如计算某年有多少钱(因为money类型为BigDecimal类型,所以需要映射)
Map<String,Optional<BigDecimal>> item = list.stream() .collect(Collectors.groupingBy(List::getYear,Collectors.mapping(List::getMoney,  Collectors.reducing(BigDecimal::add))));
  • 只是根据某个字段进行分组:
Map<String,List<ListDto>> monthMap = list.stream().collect(Collectors.groupingBy(ListDto::getMonth));

并行流

// API :
Stream<E> parallelStream()

// 例子 :
并行流计算  parallelStream()
//年加权融资成本
Map<String,BondPledgeBusinessDto>
List<BondPledgeBusinessDto>    yearTJ =
                bondPledgeBusinessDtoList.parallelStream().collect(Collectors.groupingBy(BondPledgeBusinessDto::getYear,
                        Collectors.collectingAndThen(Collectors.toList(),item->{
                          //年含权融资成本
                          BigDecimal HQRZ = item.parallelStream().map(BondPledgeBusinessDto::getHQRZCBH).reduce(BigDecimal.ZERO,BigDecimal::add);
                          //年权重
                          BigDecimal QZ = item.parallelStream().map(BondPledgeBusinessDto::getWeight).reduce(BigDecimal.ZERO,BigDecimal::add);
                          //年加权融资成本
                          BigDecimal JQZRCB = HQRZ.divide(QZ4,RoundingMode.HALF_UP);
                          BigDecimal ZSY1 = item.parallelStream().map(BondPledgeBusinessDto::getSY).reduce(BigDecimal.ZERO,BigDecimal::add);
                          return new BondPledgeBusinessDto();
                        }))
                ).entrySet()
                .stream()
                .map(Map.Entry::getValue)
                .collect(Collectors.toList());

flatMap()

flatMap()操作得作用是对流得元素进行一对多转换,将结果元素展平为新的流。
Stream.flatMap()有助于将Collection<Collection<T>>转换为Collection<T>

  • flatMap()= map()+展平
  1. 例子:
//Before flattening   : [[1,2,3], [4,5], [6,7,8]]
//After flattening    : [1,2,3,4,5,6,7,8]

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

List<List<Integer>> listOfLists = Arrays.asList(list1, list2, list3);

List<Integer> listOfAllIntegers = listOfLists.stream()
        .flatMap(x -> x.stream())
        .collect(Collectors.toList());
/*或者
String[][] dataArray = new String[][]{{"1","2","3"}, {"4","5"}, { "6","7","8"}};

List<String> listOfAllChars = Arrays.stream(dataArray)
        .flatMap(x -> Arrays.stream(x))
        .collect(Collectors.toList());

System.out.println(listOfAllChars);
*/        
System.out.println(listOfAllIntegers);

peek()
peek(Consumer action)方法,该方法在应用方法参数Consumer Action之后返回一个包含原始流的所有元素的新流

List<Integer> list = Arrays.asList(1,2,3,4,5); 
List<Integer> newList = list.stream()
                    .peek(System.out::println)
                    .collect(Collectors.toList());//没有任何终端操作的`Stream.peek()`不会执行任何操作
              System.out.println(newList);  

/*
1
2
3
4
5
[1,2,3,4,5]
*/

skip()
stream.skip(long n)方法从流中跳过第n个元素。skip()方法返回一个由该流得其余元素组成的流。
skip(long n)在按encounter order丢弃流的前'n'元素后,返回由流的其余元素组成的流。

//例子
Stream<Integer> evenNumInfiniteStream = Stream.iterate(0 , n -> n +  2);
List<Integer> newList = evenNumInfiniteStream
                        .skip(5)
                        .limit(10)
                        .collect(Collectors.toList());

System.out.println(newList);

/*[10,12,14,16,18,20,22,24,26,28]*/

forEachOrdered

foreachored()方法对该流中的每个元素执行一个操作,确保对具有定义的遇到顺序的流按遇到顺序处理每个元素。

toArray()
Stream.toArray()方法将流转换为数组

    `IntStream infiniteNumberStream = IntStream.iterate(1, i -> i+``1``);
    Integer[] integerArray = infiniteNumberStream.limit(10)
                                                 .boxed()
                                           .toArray(Integer[]::new);
System.out.println(Arrays.toString(integerArray));
/**
[1,2,3,4,5,6,7,8,9,10]
*/

我们可以使用Stream.filter()方法传递一个字段该将仅返回与该前置条件匹配的那些元素。

List<Employee> employeeList = new ArrayList<>(Arrays.asList(
        new Employee(1, "A", 100),
        new Employee(2, "B", 200),
        new Employee(3, "C", 300),
        new Employee(4, "D", 400),
        new Employee(5, "E", 500),
        new Employee(6, "F", 600)));

Employee[] employeesArray = employeeList.stream()
        .filter(e -> e.getSalary() < 400)
        .toArray(Employee[]::new);

System.out.println(Arrays.toString(employeesArray));

推荐帖子:Java8 Stream 数据流,大数据量下的性能效率怎么样? blog.csdn.net/weixin_3840…