Stream流

文章目录

  • Stream流
    • 1. 集合遍历
    • 2. 流式思想
    • 3. Stream流
      • 3.1 概念
      • 3.2 流的获取
      • 3.3 forEach
      • 3.4 filter
      • 3.5 map
      • 3.6 count
      • 3.7 limit
      • 3.8 skip
      • 3.9 concat

1. 集合遍历

根据前面所学的内容可知,如果想要使用某个数据结构来存储一些类型的元素,我们可以选择数组或者Java提供的多种类型的集合,如ArrayList、HashSet和HashMap等。针对于不同的类型,遍历的方法也有多种可供选择,下面我们依次回顾一下。

  • 数组的遍历:数组的遍历可以使用传统的for循环(对比于for-each而言)或者是增强for循环,即for-each循环

    public class ListDemo {public static void main(String[] args) {int[] array = new int[]{1, 3, 10, 6, 2};for (int i = 0; i < array.length; i++) {System.out.println(array[i]);}for(int ele:array){System.out.println(ele);}}
    }
    
  • 单列集合的遍历:常用的单列集合有List类型和Set类型两种,集合遍历的方法有for-each循环和使用迭代器两种

    • 使用for-each循环

      public class ListDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Forlogen");list.add("kobe");list.add("James");Iterator<String> iter  = list.iterator();while (iter.hasNext()){System.out.println(iter.next());}for(String ele:list){System.out.println(ele);}}
      }
      

    浅析Java中的Collection

    浅析Java中的Iterator

    • 双列集合的遍历:常用的双列集合有Map等,针对Map又有两种方法遍历集合中的元素

      • 使用Set<K> keySet()获取集合中的所有键,然后使用get(Object key)根据指定的键获取对应的值

        import java.util.HashMap;
        import java.util.Map;
        import java.util.Set;public class ListDemo {public static void main(String[] args) {Map<Integer,String> m = new HashMap<>();m.put(10, "Forlogen");m.put(23, "James");m.put(24, "kobe"); Set<Integer> keys = m.keySet();for(Integer ele : keys){System.out.println(m.get(ele));}}
        }
        
      • 使用Map集合内部的Entry对象来进行遍历:首先使用entrySet()获取保存Entry对象的Set集合。然后遍历集合中的Entry对象,使用getKey()getValue()获取每个对象的键和值

        import java.util.HashMap;
        import java.util.Map;
        import java.util.Set;public class MapMain {public static void main(String[] args) {Map<Integer,String> m = new HashMap<>();m.put(23, "James");m.put(24, "kobe"); Set<Map.Entry<Integer, String>> entries = m.entrySet();for (Map.Entry ele: entries) {System.out.println(ele.getKey() + " = " + ele.getValue()); }}
        }

      浅析Java中的Map

从上面的总结中可以看出,针对于不同的数据结构有着多种遍历方法可供选择,但循环遍历有什么弊端呢,或者说有没有更好的方式来遍历元素呢?要理解这个问题,我们需要问自己一个问题:我们遍历元素目的是为了什么?不管是简单的直接输出遍历得到的每个元素,还是对每个元素再执行附加的更多操作,我们的目的都是使用遍历得到的元素,而不关心是使用什么样的方式进行遍历

如下所示,针对于数组的遍历来说,我们的目的是使用遍历得到的array[i],而不管是用传统的for循环还是for-each循环。具体到传统的for循环,for循环的语法表示的是怎么做(从头到尾依次取数组中对应索引的元素),而循环体表示的是做什么(直接输出数组元素)。前者是方式,后者是目的!

int[] array = new int[]{1, 3, 10, 6, 2};
for (int i = 0; i < array.length; i++) {System.out.println(array[i]);
}

下面我们通过一个更复杂的例子来感受一下,什么是只关心做什么而不关心怎么做。假设现有一个String类型的集合,我们希望对集合中的元素进行过滤,分三步操作:

  • 选出长度大于3的元素
  • 选出字符串首字母为"b"的元素
  • 最后遍历输出过滤后集合中的元素

如果使用循环遍历,我们只能这样做:

import java.util.ArrayList;
import java.util.List;public class ListDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Forlogen");list.add("kobe");list.add("James");list.add("ball");list.add("bill");// 1List<String> list2 = new ArrayList<>();for(String ele:list){if (ele.length() > 3){list2.add(ele);}}// 2List<String> list3 = new ArrayList<>();for (String ele: list2) {if (ele.startsWith("b")){list3.add(ele);}}// 3for(String ele:list3){System.out.println(ele);}}
}

从上面的代码中可以看出,整个过程使用了3个ArrayList存储元素,以及使用了3次的循环遍历。而我们的目的只是为过滤集合中的元素,因此能否有更好的方式来完成上面同样的任务呢?

根据前面所学的函数式编程思想Lambda表达式函数式接口可知,它们关注的就是做什么而不是怎么做。对比来看,发现我们的诉求和函数式编程思想是一致的。因此,Java在JDK8之后引入了Stream流来实现这种诉求,方便用户使用更优雅的代码完成相同的功能。例如,如果上面的示例使用Stream流来做,可以写成如下的形式:

import java.util.ArrayList;
import java.util.List;public class ListDemo {public static void main(String[] args) {List<String> list = new ArrayList<>();list.add("Forlogen");list.add("kobe");list.add("James");list.add("ball");list.add("bill");list.stream().filter((name)->name.length() > 3).filter((name)->name.startsWith("b")).forEach((name)-> System.out.println(name));}
}

我们只需要一行代码就可以代替上面三个for-each循环所做的事,不仅优雅,而且高效。


2. 流式思想

如何理解Stream流所代表的流式思想呢?首先,我们要将其和前面所学的IO流中的流区分开,然后再去理解流式思想。根据上面集合遍历的例子可以看出,流式思想极像生产中的流水线,每个流模型都只负责它所精通的工作,同时不需要额外的内存空间存储所处理后的结果。以我们生活中的做饭为例,做一顿饭需要买菜、洗菜、切菜、炒菜和装盘,每个步骤都是使用上一个步骤的结果,但是每个步骤处理完菜之后就交给下一个步骤,自己并不会留着菜。


source

其实流式思想广泛在多个领域使用,如数据科学中数据预处理的pipeline,NLP或者CV领域中对于数据集中数据的预处理等等。只要我们设计好了整体的pipeline,我们只需要关心最后得到的结果,而不必在意中间的处理步骤。以NLP中对于语料库中数据的预处理为例,通常需要执行去除无用字符→分词→词干提取→构建词汇表等操作,我们需要使用的是整个pipeline处理后的结果,而不关注中间步骤数据被处理成什么样,因此中间步骤也没有必要保存处理后的结果。

3. Stream流

3.1 概念

pipeline中的每一个阶段都是一个流模型,通过调用pipeline中设计的方法可以实现一个流到另一个流的转换。Java中的Stream流可以看成是一个来自数据源的元素队列,其中:

  • 元素:指特定类型的对象,所有的元素会形成一个队列,但Stream流并不存储元素
  • 数据源:指流中数据的来源,如数组、集合等

Stream流具有两个基础特征:

  • Pipelining:从Stream的思想可知,中间的每个操作都会返回流对象本身,多个操作就可以构成类似于流水线的模型
  • 内部迭代:不同于之前的遍历方法,Stream流可以直接调用遍历方法

因此,Java中要想使用Stream流,通过需要三个步骤:

  • 获取Stream流中的数据源
  • 执行Stram流中的转换操作
  • 获取想要的结果

每次转换原有的Stream对象并不发生改变,而是返回一个新的流对象。

3.2 流的获取

Java中获取Stream流有两种方式:

  • 所有的Collection集合都可以通过stream()获取对应的流

    如ArrayList的stream()源码如下:

    default Stream<E> stream() {return StreamSupport.stream(spliterator(), false);
    }
    
  • 使用java.util.stream.Stream<T>接口中的静态方法of()获取数组对应的流,由于方法中的参数为可变参数,因此可以传递数组

    static <T> Stream<T> of (T...values)
    

对于单列集合来说,可以直接使用stream()方法获取流:

List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();Set<String> set = new HashSet<>();
Stream<String> stream2 = set.stream();

对于Map这样的双列集合来说,它需要先通过keySet()获取键、通过values()获取值或者使用entrySet()获取所有键值之间的映射关系,由于它们的返回值都是Set集合,因此可以再使用stream()获取流。

// Map集合需要先转换为Set集合,再通过set集合的stream()获取流
Map<Integer, String> map = new HashMap<>();
// 获取键
Set<Integer> keys = map.keySet();
Stream<Integer> steam3 = keys.stream();
// 获取值
Collection<String> values = map.values();
Stream<String> stream4 = values.stream();
// 获取键值映射关系
Set<Map.Entry<Integer, String>> entries = map.entrySet();
Stream<Map.Entry<Integer, String>> stream5 = entries.stream();

对于不同类型的数组来说,可以使用Stream接口中的of()来获取流。

Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5);
Integer[] arr = {1, 2, 3, 4, 5};
Stream<Integer> stream7 = Stream.of(arr);

Stream流中包含有众多的方法,这些方法可以分为两类:

  • 延迟方法:返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用,如filter()limit()
  • 终结方法:返回值类型不再是Stream接口自身类型的方法,因此无法再调用其他的流模型,如count()forEach()
3.3 forEach

Stream流中的forEach虽然看起来像和for-each循环完成的是同样的工作,但原理有所不同。方法接收一个Consumer接口函数,会将每一个流元素将给该函数进行处理。

void forEach(Consumer<? super T> action)
3.4 filter

filter方法用于对Stream流中的数据进行过滤,它接收的是一个Predicate接口函数,然后对于每一个流元素进行判断,返回的流中只保留符合条件的元素。

Stream<T> filter(Predicate<? super T> predicate)
3.5 map

map方法将流中的元素映射到另一个流中,它使用Function接口函数来实现元素类型的转换,从而实现流之间的元素的映射。

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

count方法是一个终结方法,它用于统计Stream流中元素的个数。

long count()
3.7 limit

limit方法用于截取流中的元素,只取用前n个。它是一个延迟方法,只是对流中的元素进行截取,返回一个新的流。

Stream<T> limit(long maxSize)

如果当前集合长度大于maxSize则进行截取,否则不进行操作

3.8 skip

skip方法用于跳过元素,返回的是一个新的流。

Stream<T> skip(long n)

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

3.9 concat

concat方法用于合并两个流,它是Stream接口中的静态方法,因此可以使用接口名直接调用。

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

完整的实验代码:

import java.util.stream.Stream;public class SomeFuncInStream {public static void main(String[] args) {UsingForEach();System.out.println("-----------------");UsingFilter();System.out.println("-----------------");UsingMap();System.out.println("-----------------");UsingCount();System.out.println("-----------------");UsingLimit();System.out.println("-----------------");UsingSkip();System.out.println("-----------------");UsingConcat();}private static void UsingConcat() {Stream<String> stream = Stream.of("Forlogen", "kobe");Stream<String> stream1 = Stream.of("James", "ball");Stream.concat(stream, stream1).forEach(name-> System.out.println(name));}private static void UsingSkip() {Stream<String> stream = Stream.of("Forlogen", "kobe", "James");stream.skip(1).forEach(name-> System.out.println(name));}private static void UsingLimit() {Stream<String> stream = Stream.of("Forlogen", "kobe", "James");stream.limit(2).forEach(name-> System.out.println(name));}private static void UsingCount() {Stream<String> stream = Stream.of("Forlogen", "kobe", "James");long count = stream.filter(name -> name.length() > 4).count();System.out.println(count);}private static void UsingMap() {Stream<String> stream = Stream.of("1", "2", "3");stream.map(s->Integer.parseInt(s)).forEach(x-> System.out.println(x));}private static void UsingFilter() {Stream<String> stream = Stream.of("Forlogen", "kobe", "James");stream.filter(name->name.length() > 4).forEach(name-> System.out.println(name));}private static void UsingForEach() {Stream<String> stream = Stream.of("Forlogen", "kobe", "James");stream.forEach(name -> System.out.println(name));}}

输出为:

Forlogen
kobe
James
-----------------
Forlogen
James
-----------------
1
2
3
-----------------
2
-----------------
Forlogen
kobe
-----------------
kobe
James
-----------------
Forlogen
kobe
James
ball

浅析Java中的函数式接口

浅析Java中的Steam流相关推荐

  1. java中的steam流

      当需要对多个元素进行操作(特别是多步操作)的时候,考虑到性能及便利性,我们应该首先拼好一个"模型"步骤方案,然后再按照方案去执行它   这张图展示了过滤 映射 跳过 计数等多步 ...

  2. java 流的概念_举例讲解Java中的Stream流概念

    1.基本的输入流和输出流 流是 Java 中最重要的基本概念之一.文件读写.网络收发.进程通信,几乎所有需要输入输出的地方,都要用到流. 流是做什么用的呢?就是做输入输出用的.为什么输入输出要用&qu ...

  3. 浅析Java中的final关键字

    浅析Java中的final关键字 谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来 ...

  4. 四十三、深入Java中的数组流,数据流和对象流操作

    @Author:Runsen @Date:2020/6/8 作者介绍:Runsen目前大三下学期,专业化学工程与工艺,大学沉迷日语,Python, Java和一系列数据分析软件.导致翘课严重,专业排名 ...

  5. Java中的IO流(六)

    上一篇<Java中的IO流(五)>把流中的打印流PrintStream,PrintWriter,序列流SequenceInputStream以及结合之前所记录的知识点完成了文件的切割与文件 ...

  6. java printf与println_浅析Java中print、printf、println的区别

    我们的程序员在开发的时候,都会使用到很多不同的功能,但是有些功能是大同小异,别着急,下文是爱站技术频道小编为大家带来的浅析Java中print.printf.println的区别,希望对你学习有帮助! ...

  7. 【转】浅析Java中的final关键字

    谈到final关键字,想必很多人都不陌生,在使用匿名内部类的时候可能会经常用到final关键字.另外,Java中的String类就是一个final类,那么今天我们就来了解final这个关键字的用法. ...

  8. Java中使用字符流读取UTF-8和写出txt文件 乱码 问题

    乱码问题一直都是非常难受的问题,本文解决Java中使用字符流读取UTF-8和写出txt文件 乱码 话不多说,直接上图 输出结果: 使用代码: 解决:

  9. java io流分为,Java中的IO流按照传输数据不同,可分为和

    Java中的IO流按照传输数据不同,可分为和 答:字节流 字符流 克里斯蒂安 · 麦茨指出:想象的能指就是电影的能指,作为象征的科学,在第三视野范围内的解读,它是( ) 答:建立在共同的永久的背景之中 ...

最新文章

  1. Android 10 vivo,更快更安全,vivo产品经理宣布:iQOO将首批适配Android 10正式版
  2. 【新周报(050)】Datawhale组队学习
  3. cat命令分析_学习记录
  4. PaaS服务之路漫谈(一)
  5. Spring MVC--使用Jackson返回JSON格式数据
  6. python图片保存重命名_Python实现重命名一个文件夹下的图片
  7. k 近邻算法解决字体反爬手段|效果非常好
  8. 【clickhouse】clickhouse 如何实现tcp方式发送数据
  9. python提取excel数据视频_Python-爬取b站的热门视频并导入Excel中
  10. 湖北网络安全的产业机遇在哪里
  11. php 把java list对象转成数组,java_JSON的String字符串与Java的List列表对象的相互转换,在前端: 1.如果json是List对象 - phpStudy...
  12. Android在线电影播放器案例
  13. html轮播图代码自适应,【简易轮播代码】自适应全屏轮播banner图切换代码
  14. CSP 202112-3 登机牌条码
  15. python绘图--由逐日风场数据计算月平均风场数据并绘制二维填色图
  16. Detach Procedure
  17. Moveit报错:Unable to identify any set of controllers that can actuate the specified joints
  18. c++二维矩阵顺时针和逆时针旋转
  19. 这款录屏神器在 GitHub 火了,秒杀 33 种同类工具!
  20. 小米官宣:手表Color 2来了,6色多彩表带,你喜欢哪个?

热门文章

  1. 最全面的行人重识别数据集汇总
  2. 电子沙盘虚拟数字沙盘培训教程第3课
  3. 转载---保证你35岁以前成功的经典秘籍
  4. UML简单介绍-如何看懂UML(一)
  5. 幼儿园里的“社交尴尬”,这样处理!
  6. Inkscape矢量抠图教程
  7. 计算机键盘打字基础知识,电脑键盘基础知识
  8. Android 保存图片到相册
  9. 中概股回归难逆袭 陌陌私有化就遇到了失败风险
  10. Project 2013 2003