Jdk8中Stream的简介

前言

在jdk8环境中, 演示Stream的基本操作; 读者需要了解Lambda表达式的用法, 作为阅读本文的预备知识.

演示的github链接

本文内容基本借鉴于 慕课网, 感谢!

Stream 简介

java8中, 流的操作基本分三部分:

  • 构建流: 流的来源可能是集合, 数组, 文件, 其他来源暂不讨论
  • 中间操作: 可以从原始的流开始, 执行多次, 每次执行返回一个新的流
  • 终端操作: 只能在流的最后, 执行一次的操作

注意以下几点:

  • 流不存储或改变其元素, 只返回新流或执行流中定义的操作
  • 流操作是惰性的, 只有终端操作被执行时, 其中间操作才执行
  • 所有流操作以函数式接口(lambda表达式) 作为参数

构建流

1.由数值或对象直接构建流:

	@Test
    public void streamFromValue() {
        Stream stream1 = Stream.of(1, 2, 3, 4, 5);
        Stream stream2 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
//        Stream<Integer> stream3 =Stream.of(90, -2L, 2d,"22",new Integer(8), new Date());
        stream1.forEach(System.out::println);
        System.out.println("-------------");
        stream2.forEach(System.out::println);
    }

结果:

1
2
3
4
5
-------------
90
-2
2.0
22
8
Sat Nov 09 19:04:09 CST 2019

注意到, 可以给流添加泛型, 而统一流元素的类型; 不加泛型时, 可传入任何类型

2.由数组构建流:

	@Test
    public void streamFromArray() {
        int[] numbers = {1, 2, 3, 4, 5};
        IntStream stream = Arrays.stream(numbers);
        stream.forEach(System.out::println);
    }

观察Arrays.stream方法的源码:

	public static <T> Stream<T> stream(T[] array) {
        return stream(array, 0, array.length);
    }

可以看到, 数组元素的类型和它所生成流的泛型保持一致;

如果数组是基本类型的, 所生成流的泛型是相应的包装类

3.通过文件生成流

	@Test
    public void streamFromFile() throws IOException {
        // TODO 此处替换为本地文件的地址全路径
        String filePath = "";
        Stream<String> stream = Files.lines(
                Paths.get(filePath));
        stream.forEach(System.out::println);
    }

这个不多说了, 试下就知道

4.通过集合生成流(常见)
jdk8的Collection类中, 有一个default 的stream方法:

	default Stream<E> stream() {
        return StreamSupport.stream(spliterator(), false);
    }

可以看到, 任何集合通过stream() 方法, 即可获得拥有相同泛型的流; 这个用法后面演示会涉及, 就不赘述了

准备一个用于测试的集合

再贴一下 演示的github链接, 在sku包里都有.

看一下这个集合的全貌:

@Test
    public void all(){
        list.stream()
                .forEach(System.out::println);
    }

结果是9个购物车中的商品:
在这里插入图片描述

有状态操作(串行) 和无状态操作(并行)的区别

有状态操作, 就是需要收集流中所有元素后, 才能正确完成的操作(官方说法是: 需要内部状态来累计计算结果); 无状态操作, 就是除了有状态操作的其他中间操作, 也可以理解成, 只依赖当前元素, 就能正确完成的操作.
在这里插入图片描述
因此, distinct()去重, sorted() 排序, limit(int i) 截断只留前 i个, skip(int i) 跳过前 i个, 都是不可能依赖单个元素能正确完成的; 余下的中间流都属于无状态的

可能有人对filter() 有疑问, 其实 filter() 是逐个判断流元素是否符合断言, 从而达到筛选效果, 可以理解成"筛出来一个, 就放行一个".

演示下有状态操作 sort(), 这里的演示中, 读者不必在意流参数组织的形式, 而是注意有状态操作和无状态操作的区别:

@Test
    public void sortedAll() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
                .sorted(Comparator.comparing(sku -> sku.getSkuId()))
                .forEach(sku -> System.out.println("__" + sku.getSkuId() + "__"));
    }

在这里插入图片描述
可以看到, SkuId在执行了sort()前先被peek()输出, 然后执行sort()后, 正确地从小到大输出

演示下无状态操作, 注意和上面的有状态操作的对比:

@Test
    public void filterTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                .filter(sku -> null != sku)
                .forEach(sku -> System.out.println("__" + sku.getSkuName() + "__"));

    }

在这里插入图片描述
显然, 这次不是整个流都执行完peek(), 再执行filter()了. 这里peek()和 filter()都是无状态流, 是并行流. 它们执行完一个元素就放行一个; 相反, 串行流执行完整个流, 再放行整个流

常用无状态操作(并行操作)演示

filter(Predicate<? super T> predicate)

打开看Stream接口源码中的 filter, 可以看到filter需要参数为断言型接口, 返回值仍是Stream类型的

@Test
    public void filterTest2() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
                .filter(sku -> sku.getSkuCategory().equals(SkuCategoryEnum.BOOKS))
                .forEach(System.out::println);
    }

在这里插入图片描述
从演示结果中, 看到filter操作是并行操作, 且将符合断言型接口的元素, 放置到返回的流中

map(Function<? super T, ? extends R> mapper)

map的参数是一个函数式接口, 其功能是, 将一个元素改变后, 放入作为返回值的流; 因此map具有转换流的作用

@Test
    public void mapTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))		//简单输出下元素, 但经过此操作, 流不会改变
                .map(sku -> "__" + sku.getSkuName() + "__")
                .forEach(System.out::println);
    }

在这里插入图片描述
可以看到, map(Function<? super T, ? extends R> mapper) 依然是并行操作

flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

flatMap也是转换流元素的中间操作, 但是它可以把一个元素转换成多个元素的组成的流; 传入参数依然是函数式接口

@Test
    public void flatMapTest() {
        list.stream()
                .peek(sku -> System.out.println())      //空输出, 只用作换行
                .peek(sku -> System.out.println(sku.getSkuName()))
                .flatMap(sku -> Arrays.stream(sku.getSkuName().split("")))
                .forEach(ch -> System.out.print(ch +"\t"));
    }

在这里插入图片描述
可以看到, flatMap是并行操作, 它把接收到的单个元素转换成流返回

peek(Consumer<? super T> action)

peek的功能经过前几个例子, 大家应该很熟悉了, 其特点是针对流进行指定操作, 操作后流不会被改变.

但是这样说并不确切, 因为peek是典型的无状态操作, 每次只针对一个元素. 实际上对其他无状态操作, 如 filter, map, 都是改变当前操作元素, 并放到作为返回值的流中, 以至于看上去整个流被改变了; peek是直接把当前元素放到作为返回值的流中, 所以看上去流没有改变.

DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper)

有一类特殊的中间操作, 可以把输入元素转换成指定类型元素. 相当于指定返回元素
类型的map操作

@Test
    public void mapTest1(){
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuId()))
//                .mapToDouble(sku -> sku.getSkuName())   //转换成错误的类型, 编译报错
                .mapToDouble(sku -> sku.getSkuId())
                .forEach(System.out::println);
    }

在这里插入图片描述

常用有状态操作(串行操作)演示

sorted(Comparator<? super T> comparator) 和 sorted()

@Test
    public void sortTest() {
        list.stream()
                .peek(sku -> System.out.println(sku.getSkuName()))
                .sorted(Comparator.comparing(Sku::getSkuId).reversed())
                .forEach(System.out::println);
    }

在这里插入图片描述
看结果可知, sorted(Comparator<? super T> comparator) 方法根据传入的Comparator接口, 对流元素进行排序, 使用 reversed() 可以指定逆序排序

再看 sorted() 操作:

@Test
    public void sortTest1(){
        list.stream()
                .map(sku -> sku.getSkuPrice())
                .sorted()	// 等同于 sorted((c1, c2) -> c1.compareTo(c2))
                .forEach(System.out::println);
    }

注意sorted() 接收的流元素所在的类, 必须实现Comparable 接口, 否则运行时, 无法知道比较的依据:

@Test
    public void sortTest2(){
        list.stream()
                .sorted()    // 等同于 sorted((c1, c2) -> c1.compareTo(c2))
                .forEach(System.out::println);
    }

在这里插入图片描述

distinct() 去重操作

@Test
    public void distinctTest() {
        list.stream()
                .map(sku -> sku.getSkuCategory())
                .distinct()
                .forEach(System.out::println);
    }

在这里插入图片描述

skip(long n), 对整个流跳过指定的前几个元素, 再返回

@Test
    public void skipTest() {
        list.stream()
                .sorted(Comparator.comparing(sku -> sku.getTotalPrice()))
                .skip(3)
                .forEach(System.out::println);
    }

按总价排序, 再跳过总价最低的前三个
在这里插入图片描述

limit(long maxSize), 截取整个流的前若干个元素, 返回

@Test
    public void limitTest() {
        list.stream()
                .sorted(Comparator.comparing(Sku::getTotalPrice))
                .skip(2 * 3)
                // limit
                .limit(3)
                .forEach(System.out::println);
    }

在这里插入图片描述
可以看到, skip(long n)结合 limit(long n)使用, 可以模拟分页效果

常用终端操作演示

boolean allMatch(Predicate<? super T> predicate)
boolean anyMatch(Predicate<? super T> predicate)
boolean noneMatch(Predicate<? super T> predicate)

@Test
    public void allMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .allMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }

在这里插入图片描述
可以看到, allMatch(Predicate<? super T> predicate) 根据断言型接口筛选元素, 都匹配为true, 如果遇到不匹配的第一个元素, 立即短路(不再选取下一个元素), 并返回false

anyMatch(Predicate<? super T> predicate), 任何一个匹配就短路, 返回true; noneMatch(Predicate<? super T> predicate), 任何一个匹配就短路, 返回 false, 直接把测试代码贴在下面:

@Test
    public void anyMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .anyMatch(sku -> sku.getTotalPrice() > 100);
        System.out.println(match);
    }

@Test
    public void noneMatchTest() {
        boolean match = list.stream()
                .peek(System.out::println)
                .noneMatch(sku -> sku.getTotalPrice() > 3000);
        System.out.println(match);
    }

collect() 方法演示

collect() 方法可以把流变成集合返回, 也可以把流按条件分组, 变成Map返回, 下面是两个示例, 了解就可以

@Test
    public void toList() {
        List<Sku> list = CartService.getCartSkuList();
        List<Sku> result = list.stream()
                .filter(sku -> sku.getTotalPrice() > 100)
                .collect(Collectors.toList());
        result.stream()
                .forEach(System.out::println);
    }

在这里插入图片描述

@Test
    public void group() {
        List<Sku> list = CartService.getCartSkuList();
        Map<Object, List<Sku>> group = list.stream()
                .collect(Collectors.groupingBy(
                                sku -> sku.getSkuCategory()));  //以类别为条件, 为list分组
        for(Map.Entry entry: group.entrySet()){
            System.out.println(entry.getKey());
            ((List)entry.getValue()).stream()
                    .forEach(System.out::println);
        }
    }

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值