Steram流式计算学习笔记

本文介绍了Java8中的Stream API,它提供了流式处理的概念,使得集合操作更加简洁高效。通过实例展示了如何使用filter、map、forEach等方法进行数据过滤、转换和处理,并解释了Stream的延迟执行特性以及获取流、常用方法等核心概念。Stream通过函数式编程,实现了类似工厂流水线的多步操作,提高了代码的可读性和性能。
摘要由CSDN通过智能技术生成

Steram流式计算

一、概述

说到Stream便容易想到 I/O Stream,而实际上,谁规定"流"就一定是"I0流"呢 ?在Java 8中,得益于Lambda所带来的函数式编程,引入了一个全新的Stream概念,用于解决已有集合类库既有的弊端。

什么是Stream流式计算
大数据∶存储+计算

集合、MySQL本质就是存储东西的;

计算都应该交给流来操作!

例子:

在这里插入图片描述

1.1、引言

传统集合的多步遍历代码
几乎所有的集合(如collection接口或 Map 接口等)都支持直接或间接的遍历操作。而当我们需要对集合中的元素进行操作的时候,除了必需的添加、删除、获取外,最典型的就是集合遍历。例如∶

 public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("李白");
        arrayList.add("王昭君");
        arrayList.add("韩信");
        arrayList.add("刘邦");
        // foreach 输出
        for (String s : arrayList) {
            System.out.println(s);
        }
 }

这是一段非常简单的集合遍历操作∶对集合中的每一个字符串都进行打印输出操作。

循环遍历的弊端

Java 8的Lambda让我们可以更加专注于做什么( What ),而不是怎么做( How ),这点此前已经结合内部类进行了对比说明。现在,我们仔细体会一下上例代码,可以发现∶

  • for循环的语法就是“怎么做”
  • for循环的循环体才是“做什么”

为什么使用循环?因为要进行遍历。但循环是遍历的唯一方式吗? 遍历是指每一个元素逐一进行处理,而并不是从第一个到最后一个顺次处理的循环。前者是目的,后者是方式。

试想一下,如果希望对集合中的元素进行筛选过滤∶

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

那怎么办?在Java 8之前的做法可能为∶

    public static void main(String[] args) {
        ArrayList<String> arrayList = new ArrayList<>();
        arrayList.add("李白");
        arrayList.add("李世民");
        arrayList.add("王昭君");
        arrayList.add("李信");
        arrayList.add("韩信");
        arrayList.add("刘邦");
        //对arrayList中的元素进行过滤,只要以李开头的。存到一个新集合
        ArrayList<String> listA = new ArrayList<>();
        for (String s : arrayList) {
            if (s.startsWith("李")){
                listA.add(s);
            }
        }
        //对listA进行过滤,只要名字长度为3的。
        ArrayList<String> listB = new ArrayList<>();
        for (String s : listA) {
            if (s.length()==3){
                listB.add(s);
            }
        }
        //最后对满足条件的集合遍历输出
        for (String s : listB) {
            System.out.println(s);
        }
    }

这段代码中含有三个循环,每一个作用不同:

1.首先筛选所有姓李的人;

2.然后筛选名字有三个字的人;

3.最后进行对结果进行打印输出。

每当我们需要对集合中的元素进行操作的时候,总是需要进行循环、循环、再循环。这是理所当然的么? 不是。循环是做事情的方式,而不是目的。另一方面,使用线性循环就意味着只能遍历一次。如果希望再次遍历,只能再使用另一个循环从头开始。

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

Stream的更优写法

下面来看一下借助Java 8的Stream APl,什么才叫优雅∶

public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("李白");
    arrayList.add("李世民");
    arrayList.add("王昭君");
    arrayList.add("李信");
    arrayList.add("韩信");
    arrayList.add("刘邦");
    arrayList.stream()
        .filter(name->name.startsWith("李"))
        .filter(name->name.length()==3)
        .forEach(name-> System.out.println(name));
}

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

1.2、流式思想概述

注意 : 请暂时忘记对传统lO流的固有印象!

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

在这里插入图片描述

这张图中展示了过滤、映射、跳过、计数等多步操作,这是一种集合元素的处理方案,而方案就是一种"函数模型”。图中的每一个方框都是一个"流”,调用指定的方法,可以从一个流模型转换为另一个流模型。而最右侧的数字3是最终结果。这里的 filter 、map 、skip都是在对函数模型进行操作,集合元素并没有真正被处理。只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作。而这得益于Lambda的延迟执行特性。

备注 :“Stream流" 其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其地址值)。

Stream(流)是一个来自数据源的元素队列。

  • 元素是特定类型的对象,形成一个队列。Java中的Stream并不会存储元素,而是按需计算。
  • 数据源 流的来源。可以是集合,数组等。

和以前的Collection操作不同,Stream操作还有两个基础的特征︰

  • Pipelining : 中间操作都会返回流对象本身。这样多个操作可以串联成一个管道,如同流式风格( fluentstyle )。这样做可以对操作进行优化,比如延迟执行(Iaziness)和短路( short-circuiting)。

当使用一个流的时候,通常包括三个基本步骤∶获取一个数据源( source )→数据转换→执行操作获取想要的结果,每次转换原有Stream对象不改变,返回一个新的Stream对象(可以有多次转换),这就允许对其操作可以像链条一样排列,变成一个管道。

1.3、获取流

==java.util.stream.Stream==是Java 8新加入的最常用的流接口。(这并不是一个函数式接口)

获取一个流非常简单,有以下几种常用的方式∶

  • 所有的 collection集合 都可以 通过 stream() 默认方法获取流 。
  • Stream接口 的 静态方法 static <T> Stream<T> of( T... values) 可以获取数组对应的流 。
根据Collection获取流

首先,java.util.collection 接口中加入了 default Stream<E> stream() 方法 用来获取流,所以其所有实现类均可获取流。

    public static void main(String[] args) {
        //单列集合类型,直接把集合转换为Stream流
        List<String> list = new ArrayList<>();
        Stream<String> stream = list.stream();
        Set<String> set = new HashSet();
        Stream<String> stream1 = set.stream();
        //非单列集合类型,通过间接方式
            //获取键,存储到一个Set中
        Map<String,String> map = new HashMap<>();
        Set<String> keySet = map.keySet();
        Stream<String> stream2 = keySet.stream();
            //获取值,存储到一个集合中。
        Collection<String> values = map.values();
        Stream<String> stream3 = values.stream();
            //获取键值对,存储的一个集合中,
        Set<Map.Entry<String, String>> entries = map.entrySet();
        Stream<Map.Entry<String, String>> stream4 = entries.stream();

        //把数组转换为Stream流
        Stream<Integer> system5 = Stream.of(1,2,3,4,5,6);
        //可变参数可以传递数组
        Integer[] arr = {7,8,9,10,11};
        Stream<Integer> stream6 = Stream.of(arr);
        String[] arr2 = {"a","bb","ccc"};
        Stream<String> stream7 = Stream.of(arr2);
    }

备注:of方法的参数其实是一个可变参数,所以支持数组。

1.4、常用方法

在这里插入图片描述

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

  • 延迟方法∶返回值类型仍然是stream接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)
  • 终结方法︰返回值类型不再是stream接口自身类型的方法,因此不再支持类似StringBuilder 那样的链式调用。本小节中,终结方法包括countforEach方法。

备注 : 本小节之外的更多方法,请自行参考API文档。

逐一处理 : forEach[终结方法]

虽然方法名字叫forEach,但是与 for 循环中的“for-each" 昵称不同

void forEach(Consumer<? super T> actioh ) ;

该方法接收一个 Consumer 接口函数,会将每一个流元素交给该函数进行处理。

复习Consumer接口
java.util.function.Consumer<T>//接口是一个消费型接口
//Consumer接口中包含抽象方法
void accept(T t)//意为消费一个指定泛型的数据

基本使用︰

/**
*@描述: 消费型接口consumer + stream 练习
*消费型接口consumer:
 *  new Consumer<String>() {
 *  @Override
 *  public void accept(String s) {
 *        消费操作
 *     }
 *  }
 * */
public class ConsumerTest {
    public static void main(String[] args) {
         //构造stream流
        Stream<String> stream = Stream.of("张三","李四","王五","赵六");
         //利用forEach输出
        stream.forEach(name-> System.out.println(name));
    }
}
过滤 : filter

可以通过filter方法将一个流转换成另一个子集流。

stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate函数式接口参数(可以是一个Lambda或方法引用 ) 作为筛选条件。

在这里插入图片描述

复习Predicate接口

此前我们已经学习过 java.util.stream.Predicate函数式接口,其中唯一的抽象方法为︰

boolean test(T t);

该方法将会产生一个boolean值结果,代表指定的条件是否满足。如果结果为true,那么Stream流的
filter( )方法将会留用元素; 如果结果为false,那么filter方法将会舍弃元素

基本使用︰

/**
 *@描述: 判断接口Predicate + stream 练习
 *判断接口Predicate
 *  new Predicate<String>() {
 *             @Override
 *             public boolean test(String s) {
 *                判断操作
 *             }
 *         }
 */
public class PredicateTest {
    public static void main(String[] args) {
        //构造stream流
        Stream<String> stream = Stream.of("张三","李四","王五","赵六","张良","张飞");
        //利用filter()进行筛选
        Stream<String> stream1 = stream.filter(name -> name.startsWith("张"));
        //利用forEach输出
        stream1.forEach(name-> System.out.println(name));
    }
}
注意:

Stream流属于管道流,只能被消费(使用)一次

第一个Stream流调用完华方法,数据就会流转到下一个stream上。而这时第一个Stream流已经使用完毕,就会关闭了
所以第一个stream流就不能再调用方法了。

如果再调用方法,会抛出:ilegalstateException: stream has already been operated upon or closed

映射 : map

如果需要将流中的元素映射到另一个流中,向以使用==map( )==方法。

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

该接口需要一个Function 函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的流。

在这里插入图片描述

复习Function接口

此前我们已经学习过 java.util.stream.Function函数式接口,其中唯一的抽象方法为

Rapply(T t);

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射”。

基本使用︰

/**
 *@描述: 转换型接口 function + stream 练习
 *转换型接口 function
 *   new Function<String, Object>() {
 *      @Override
 *      public Object Rapply(String s) {
 *          转换操作
 *      }
 *  }
 */
public class FunctionTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("1","3","1","4");
        Stream<Integer> integerStream = stream.map(s -> Integer.parseInt(s));
        integerStream.forEach(num-> System.out.println(num));
    }
}
统计个数 : count( )[终结方法]

正如旧集合collection当中的 ==size( )==方法一样,流提供 ==count( )==方法来数一数其中的元素个数

long count();

该方法返回一个 long值 代表元素个数 (不再像旧集合那样是 int 值)。

取用前几个 : limit( )

==limit( )==方法可以对流进行截取,只取用前n个。

Stream<T> limit( long maxsize);

参数是一个Long型,如果集合当前长度大于参数贝进行截取,否则不进行操作。

limit方法是一个延迟方法 , 只是对流中的元素进行截取 , 返回的是一个新的流,所以可以继续调用Stream流中的其他方法。

跳过前几个 : skip( )

如果希望跳过前几个元素,可以使用skip方法获取一个截取之后的新流。

Stream<T> skip( long n);

如果流的当前长度大于n,则跳过前 n个;否则将会得到一个长度为0的空流。

在这里插入图片描述

组合 : concat( )

如果有两个流,希望合并成为一个流,那么可以使用 stream 接口静态方法concat()

statick<T>Stream<T> concat(Stream<? extends T> a,stream<? extends T> b)

备注 : 这是一个静态方法,与java.lang.string当中的concat( )方法是不同的。

基本使用︰

public static void main(String[] args) {
        Stream stream1 = Stream.of("李白","王昭君","李信","李世民","韩信","刘邦");
        Stream stream2 = Stream.of("唐三","弗兰德","玉小刚","唐昊");
        Stream concatStream = Stream.concat(stream1, stream2);
        concatStream.forEach(name-> System.out.println(name));
    }
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值