java基础之--Stream流详解

说到Stream便容易想到I/O Stream,而实际上,谁规定“流”就一定是“IO流”呢?在Java 8中,得益于Lambda所带 来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端,每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么?不是,循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始

1. 循环遍历与Stream对比

  • 第一步筛选出所有姓张的人
  • 第二步筛选出名字中有三个字的人
  • 第三步打印输出结果

传统循环遍历

package ListStream;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        List<String> zhangList = new ArrayList<>();
        for (String name : list) {
            if (name.startsWith("张")) {
                zhangList.add(name);
            }
        }
        List<String> shortList = new ArrayList<>();
        for (String name : zhangList) {
            if (name.length() == 3) {
                shortList.add(name);
            }
        }
        for (String name : shortList) {
            System.out.println(name);
        }
    }
}


如果希望对集合中的元素进行筛选过滤:

  1. 将集合A根据条件一过滤为子集B
  2. 然后再根据条件二过滤为子集C

Lambda的衍生物Stream能给我们带来怎样更加优雅的写法呢?

package ListStream;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("张无忌");
        list.add("周芷若");
        list.add("赵敏");
        list.add("张强");
        list.add("张三丰");
        // 仅仅一行代码就搞定了!  获取流、过滤姓张、过滤长度为3、逐一打印
        list.stream().filter(s -> s.startsWith("张")).
        filter(s -> s.length() == 3).forEach(System.out::println);
    }
}


直接阅读代码的字面意思即可完美展示无关逻辑方式的语义:获取流、过滤姓张、过滤长度为3、逐一打印。代码中并没有体现使用线性循环或是其他任何算法进行遍历,我们真正要做的事情内容被更好地体现在代码中

2. 流式思想概述

整体来看,流式思想类似于工厂车间的“生产流水线”,当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个“模型”步骤 方案,然后再按照方案去执行它

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种“函数模型”。图中的每一个方框都是一个“流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字 3是最终结果

这里的 filter 、 map 、 skip 都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法 count 执行的时候,整个模型才会按照指定策略执行操作,而这得益于Lambda的延迟执行特性
在这里插入图片描述

2.1 根据Collection获取流

首先, java.util.Collection 接口中加入了default 方法 stream 用来获取流,所以其所有实现类均可获取流

package ListStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        // list 获取流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();

        // set 获取流
        Set<String> set = new HashSet<>();
        Stream<String> stream1 = set.stream();

        // Vector 获取流
        Vector<String> vector = new Vector<>();
        Stream<String> stream2 = vector.stream();
    }
}

2.2 根据Map获取流

java.util.Map 接口不是 Collection 的子接口,且其K-V数据结构不符合流元素的单一特征,所以获取对应的流需要分key、value或entry等情况

package ListStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        Map<String,String> map = new HashMap<>();

        // 根据键取流
        Stream<String> keyStream = map.keySet().stream();

        // 根据值取流
        Stream<String> valueStream = map.values().stream();

        // 根据键值对取流
        Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();

    }
}


2.3 根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法 of

package ListStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        String[] array = {"张无忌", "张翠山", "张三丰", "张一元"};
        Stream<String> stream = Stream.of(array);
    }
}


3. 常用方法

流模型的操作很丰富,这里介绍一些常用的API,这些方法可以被分成两种:

  • 终结方法: 返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用,终结方法包括 count 和 forEach 方法
  • 非终结方法: 返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用,(除了终结方法外,其余方法均为非终结方法)

3.1 filter-count-limit

可以通过 filter 方法将一个流转换成另一个子集流,该接口接收一个 Predicate 函数式接口参数(可以是一个Lambda或方法引用)作为筛选条件

  • 过滤 : filter
  • 统计个数: count
  • 取用前几个: limit
  • 跳过前几个: skip
  • 映射: map
  • 组合: concat
  • 逐一处理: forEach

复习Predicate接口 : java基础之–常用函数式接口Predicate与Function

package ListStream;

import java.util.*;
import java.util.stream.Stream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 10:53
 */
public class StreamDemo {
    public static void main(String[] args) {
        Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
        // 通过Lambda表达式来指定了筛选的条件:必须姓张
        Stream<String> result = original.filter(s -> s.startsWith("张"));

        // 取用前两个 limit 方法可以对流进行截取,只取用前n个 ,如果集合当前长度大于参数则进行截取;否则不进行操作
        Stream<String> limit = original.limit(2);

        // 跳过前几个 如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流
        Stream<String> skip = original.skip(2);

        // 统计个数 count
        System.out.println(result.count());

        // 映射map 如果需要将流中的元素映射到另一个流中,可以使用 map 方法
        // map 方法的参数通过方法引用,将字符串类型转换成为了int类型(并自动装箱为 Integer 类对 象)
        Stream<Integer> mapResult = original.map(Integer::parseInt);

        // 如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat
        Stream<String> original2 = Stream.of("张三", "张翠山");
        Stream<String> concatResult = Stream.concat(original, original2);
        System.out.println(concatResult);

        // 该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理
        concatResult.forEach(System.out::println);
    }
}


4. 综合练习Stream

  • 第一个队伍只要名字为3个字的成员姓名
  • 第一个队伍筛选之后只要前3个人
  • 第二个队伍只要姓张的成员姓名
  • 第二个队伍筛选之后不要前2个人
  • 将两个队伍合并为一个队伍
  • 根据姓名创建 Person 对象
  • 打印整个队伍的Person对象信息

两个队伍集合代码如下

package ListStream;

import java.util.ArrayList;
import java.util.List;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 13:14
 */
public class Demo {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三"); 
    }
}

Person类

package ListStream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 13:14
 */
public class Person {
    private String name;

    public Person() {
    }

    public Person(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person{name='" + name + "'}";
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

代码实现

package ListStream;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

/**
 * @Description
 * @auther 宁宁小可爱
 * @create 2020-05-21 13:14
 */
public class Demo {
    public static void main(String[] args) {
        List<String> one = new ArrayList<>();
        one.add("迪丽热巴");
        one.add("宋远桥");
        one.add("苏星河");
        one.add("老子");
        one.add("庄子");
        one.add("孙子");
        one.add("洪七公");

        List<String> two = new ArrayList<>();
        two.add("古力娜扎");
        two.add("张无忌");
        two.add("张三丰");
        two.add("赵丽颖");
        two.add("张二狗");
        two.add("张天爱");
        two.add("张三");

        //  第一个队伍 姓名长度3 只要前三个
        Stream<String> oneResult = one.stream().filter(s -> s.length() == 3).limit(3);
        // 第二个队伍只要姓张成员 筛选后不要前两个
        Stream<String> twoResult = two.stream().filter(s -> s.startsWith("张")).skip(2);
        // 创建Person对象 打印
        Stream.concat(oneResult, twoResult).map(Person::new).forEach(System.out::println);
    }
}

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

叫我三胖哥哥

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值