Java8中的Stream流式操作教程之王者归来

前言

相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路!

1 流的创建

1.1 流的创建方法

既然需要聊聊流的操作,那么,首先还是先看看怎么创建流。

创建流的方法有三种,分别是:Stream.of()、Stream.iterate()、Stream.generate(),然后,分别看一下这三个方法的声明。

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

static <T> Stream<T> iterate(T seed, UnaryOperator<T> f)

static <T> Stream<T> generate(Supplier<T> s)

Stream.of():参数很简单,就是一系列的泛型参数。Stream.iterate():第一个参数是一个初始值,第二个参数是一个操作。

Stream.generate():参数就是一个Supplier的供给型的参数。

1.2 流的创建方法举例

/*
 * @Author 欧阳思海
 * @Description 创建流
 * @Date 11:05 2019/8/26
 * @Param []
 * @return void
 **/
 @Test
 public void testCreateStream() {
 //利用Stream.of方法创建流
 Stream<String> stream = Stream.of("hello", "world", "Java8");
 stream.forEach(System.out::println);
 System.out.println("##################");
 //利用Stream.iterate方法创建流
 Stream.iterate(10, n -> n + 1)
 .limit(5)
 .collect(Collectors.toList())
 .forEach(System.out::println);
 System.out.println("##################");
 //利用Stream.generate方法创建流
 Stream.generate(Math::random)
 .limit(5)
 .forEach(System.out::println);
 System.out.println("##################");
 //从现有的集合中创建流
 List<String> strings = Arrays.asList("hello", "world", "Java8");
 String string = strings.stream().collect(Collectors.joining(","));
 System.out.println(string);
 }

在上面的例子中,Stream.of()方法的参数是几个字符串,Stream.iterate()方法的第一个参数是初始值 10,第二个参数是在10 的基础上每次加 1 的操作,Stream.generate()的参数是用 Random 方法产生随机数。

1.3 流的创建总结

流的创建有三种方法,分别是Stream.of()、Stream.iterate()、Stream.generate(),这几个都是 Stream 类的静态方法,所以,使用起来非常的方便。

2 流的操作

在上一节中,我们知道怎么创建流了,接下来,我们就看看对流可以进行哪些操作,使用了 Stream 流之后,是否会比 Java8 之前方便很多呢?

2.1 装箱流

在处理对象流的时候,可以利用 Collectors 类的静态方法转换为集合,例如,将字符串流转换为  List ,这种方式是没有问题的。

但是,如果遇到 double流想要转换为 List 时,这是就会报错。

DoubleStream.of(1.0, 2.0, 3.0)
 .collect(Collectors.toList());//错误的写法

这种方式就是错误的,编译是不能通过的。

别慌,对于这种问题,有 3 种比较好的解决方法。

利用 boxed 方法

利用 boxed 方法,可以将 DoubleStream 转换为 Stream ,例如;

DoubleStream.of(1.0, 2.0, 3.0)
  .boxed()
  .collect(Collectors.toList());

这样就解决了上面的问题。

利用 mapToObj 方法

利用 mapToObj 方法也可以实现上面的功能,另外,也提供了 mapToInt、mapToLong、mapToDouble 等方法将基本类型流转换为相关包装类型。

DoubleStream.of(1.0, 2.0, 3.0)
  .boxed()
  .collect(Collectors.toList());

collect 方法

一般情况下,我们利用 collect 方法的时候,都是用于将流的数据收集为基本类型的集合,例如;

stream.collect(Collectors.toList())

然而,collect 方法其实还有一种更加一般化的形式,如下;

<R> R collect(Supplier<R> supplier,
   ObjIntConsumer<R> accumulator,
   BiCnsumer<R,R> combiner)

上面这种方法的第一个参数是一个供给器,相当于初始化一个容器,第二个参数是累加器,相当于给初始化的容器赋值,第三个参数是组合器,相当于将这些元素全部组合到一个容器。

下面,我们通过一个简单的例子来看看到底是怎么使用的!

List<Double> list = DoubleStream.of(1.0, 2.0, 3.0)
  .collect(ArrayList<Double>::new, ArrayList::add, ArrayList::addAll);

上面的例子我们可以看到,第一个参数:使用一个静态方法初始化一个 List 容器,第二个参数:使用静态方法 add ,添加元素,第三个参数:使用静态方法 addAll ,用于联合所有的元素。

从最后的返回值为 List,我们也可以看出,全部组合成一个初始化的 List 集合中了。

2.2 字符串与流之间的转换

这一小节主要讲解一下字符串与流之间的转换,将 String 转为流有两种方法,分别是 java.lang.CharSequence 接口定义的默认方法 chars 和 codePoints ,而将流转为字符串就是我们前面已经讲解到的方法 collect 。

/*
 * @Author 欧阳思海
 * @Description 字符串与流之间的转换
 * @Date 9:41 2019/9/2
 * @Param []
 * @return void
 **/
 @Test
 public void testString2Stream() {
 String s = "hello world Java8".codePoints()//转换成流
  .collect(StringBuffer::new,
   StringBuffer::appendCodePoint,
   StringBuffer::append)//将流转换为字符串
  .toString();

 String s1 = "hello world Java8".chars()//转换成流
  .collect(StringBuffer::new,
   StringBuffer::appendCodePoint,
   StringBuffer::append)//将流转换为字符串
  .toString();
 }

在上面的例子中,先用chars 和 codePoints 方法转换为流,然后都是利用 collect 方法再转回字符串。

2.3 流的映射 map 与 flatMap

流的映射是什么意思呢,我们先将一个在 Java8 之前的例子,我们常常需要将一个集合的对象的某一个字段取出来,然后再存到另外一个集合中,这种场景我们在 Java8 之前我们会这样实现。

/*
 * @Author 欧阳思海
 * @Description Java8之前的用法
 * @Date 19:31 2019/9/2
 * @Param []
 * @return void
 **/
 @Test
 public void testList() {
 List<Person> list = new ArrayList<>();
 List<Friend> friends = new ArrayList<>();
 friends.add(new Friend("Java5"));
 friends.add(new Friend("Java6"));
 friends.add(new Friend("Java7"));
 Person person = new Person();
 person.setFriends(friends);
 list.add(person);

 List<String> strings = new ArrayList<>();

 for(Person p : list){
  strings.add(p.getName());
 }
 }

是不是这样很麻烦,这也就是以前大家一直所说的 Python 用一招,Java 需要用花招!

但是,Java8 却改变了这种现实,我们来看一看怎么使用 map 和 flatMap。

首先,我们先看一下这俩个方法的声明;

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

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

接下来,我们用这两个方法改写上面的方式,先看看 map 方法;

/*
 * @Author 欧阳思海
 * @Description map、flatMap方法
 * @Date 9:50 2019/9/2
 * @Param []
 * @return void
 **/
 @Test
 public void testMapAndFlatMap() {
 List<Person> list = new ArrayList<>();
 List<Friend> friends = new ArrayList<>();
 friends.add(new Friend("Java5"));
 friends.add(new Friend("Java6"));
 friends.add(new Friend("Java7"));
 Person person = new Person();
 person.setFriends(friends);
 list.add(person);

 //映射出名字
 List<String> strings = list.stream().map(Person::getName).collect(Collectors.toList());
 }

通过使用 map 方法,参数给定 Person::getName 映射出 name,然后再用 collect 收集到 List 中,就完成了上面的负责的操作,是不是很舒服。

但是,如果我们用 map 方法想要映射出 friends 属性,会遇到一个问题;

//映射出朋友
  List<List<Friend>> collect = list.stream().map(Person::getFriends).collect(Collectors.toList());

我们发现,上面的返回值是 List>,这种形式集合里面还包着集合,处理有点麻烦,但是,不是还有另外 flatMap 没有使用吗,这个方法正好能够解决这个问题。

List<Friend> collect1 = list.stream().flatMap(friend -> friend.getFriends().stream()).collect(Collectors.toList());

发现,这个方法的返回值是 List,正如我们看到的,flatMap 的方法能够“展平”包裹的流,这就是 map 和 flatMap 的区别。

2.4 流的连接

流的连接有两种方式,如果是两个流的连接,使用 Stream.concat 方法,如果是三个及三个以上的流的连接,就使用 Stream.flatMap 方法。

/**
  * @return void
  * @Author 欧阳思海
  * @Description 流的连接
  * @Date 10:13 2019/9/2
  * @Param []
  **/
 @Test
 public void testConcatStream() {
  //两个流的连接
  Stream<String> first = Stream.of("sihai", "sihai2", "sihai3");
  Stream<String> second = Stream.of("sihai4", "sihai5", "sihai6");
  Stream<String> third = Stream.of("siha7", "sihai8", "sihai9");
  Stream<String> concat = Stream.concat(first, second);

  //多个流的连接
  Stream<String> stringStream = Stream.of(first, second, third).flatMap(Function.identity());

 }

3 流的规约操作

流的规约操作几种类型,这里都讲一下。

内置的规约操作

基本类型流都有内置的规约操作。包括average、count、max、min、sum、summaryStatistics,前面的几个方法相信不用说了,summaryStatistics 方法是前面的几个方法的结合,下面我们看看他们如何使用。

/**
  * @return void
  * @Author 欧阳思海
  * @Description 内置规约操作
  * @Date 22:04 2019/9/1
  * @Param []
  **/
 @Test
 public void testReduce1() {
  String[] strings = {"hello", "sihai", "hello", "Java8"};
  long count = Arrays.stream(strings)
    .map(String::length)
    .count();
  System.out.println(count);

  System.out.println("##################");

  int sum = Arrays.stream(strings)
    .mapToInt(String::length)
    .sum();
  System.out.println(sum);

  System.out.println("##################");

  OptionalDouble average = Arrays.stream(strings)
    .mapToInt(String::length)
    .average();
  System.out.println(average);

  System.out.println("##################");

  OptionalInt max = Arrays.stream(strings)
    .mapToInt(String::length)
    .max();
  System.out.println(max);

  System.out.println("##################");

  OptionalInt min = Arrays.stream(strings)
    .mapToInt(String::length)
    .min();
  System.out.println(min);

  DoubleSummaryStatistics statistics = DoubleStream.generate(Math::random)
    .limit(1000)
    .summaryStatistics();
  System.out.println(statistics);
 }

就是这么简单!

基本的规约操作

基本的规约操作是利用前面讲过的 reduce 方法实现的,IntStream 接口定义了三种 reduce 方法的重载形式,如下;

OptionalInt reduce(IntBinaryOperator op)

int reduce(int identity, IntBianryOperator op)

<U> U reduce(U identity,
  BiFunction<U,? super T,U> accumulator,
  BianryOperator<U> combiner)

上面的 identity 参数就是初始化值的意思,IntBianryOperator 类型的参数就是操作,例如 lambda 表达式;

BianryOperator combiner是一个组合器,在前面有讲过。

下面我们通过一个例子来讲解一下。

/**
  * @return void
  * @Author 欧阳思海
  * @Description reduce规约操作
  * @Date 22:20 2019/9/1
  * @Param []
  **/
 @Test
 public void testReduce2() {
  int sum = IntStream.range(1, 20)
    .reduce((x, y) -> x + y)
    .orElse(0);
  System.out.println(sum);

  System.out.println("##################");

  int sum2 = IntStream.range(1, 20)
    .reduce(0, (x, y) -> x + 2 * y);
  System.out.println(sum2);

  System.out.println("##################");

  int sum3 = IntStream.range(1, 20)
    .reduce(0, Integer::sum);
  System.out.println(sum3);

 }

例子中的第一个是1到20累加的操作,第二个以0为初始值,然后2倍累加,第三个是以0为初始值,累加。

流的计数

流的数量统计有两种方法,分别是 Stream.count() 方法和 Collectors.counting() 方法。

/**
  * @return void
  * @Author 欧阳思海
  * @Description 统计测试
  * @Date 23:29 2019/9/1
  * @Param []
  **/
 @Test
 public void testStatistics() {
  //统计数量
  String[] strings = {"hello", "sihai", "hello", "Java8"};
  long count = Arrays.stream(strings)
    .count();
  System.out.println(count);

  System.out.println("##################");

  Long count2 = Arrays.stream(strings)
    .collect(Collectors.counting());
  System.out.println(count2);

 }

4 流的查找与匹配

流的查找

流的查找 Stream 接口提供了两个方法 findFirst 和 findAny。

findFirst 方法返回流中的第一个元素的 Optional,而 findAny 方法返回流中的某个元素的 Optional。

我们来看一个例子。

  String[] strings = {"hello", "sihai", "hello", "Java8"};
  Optional<String> first = Arrays.stream(strings)
    .findFirst();
  System.out.println(first.get());

  System.out.println("##################");

  Optional<String> any = Arrays.stream(strings).findAny();
  System.out.println(any.get());

  System.out.println("##################");

流的匹配

流的匹配 Stream 接口提供了三个方法,分别是 anyMatch(任何一个元素匹配,返回 true)、allMatch(所有元素匹配,返回 true)、noneMatch(没有一个元素匹配,返回 true)。

  boolean b = Stream.of(1, 2, 3, 4, 5, 10)
    .anyMatch(x -> x > 5);
  System.out.println(b);

  System.out.println("##################");

  boolean b2 = Stream.of(1, 2, 3, 4, 5, 10)
    .allMatch(x -> x > 5);
  System.out.println(b2);

  System.out.println("##################");

  boolean b3 = Stream.of(1, 2, 3, 4, 5, 10)
    .noneMatch(x -> x > 5);
  System.out.println(b3);

5 流的总结

这篇文章主要讲解了流的一些操作,包括下面几个方面。

- 流的创建方法。

- 流的系列操作,包括装箱流、字符串与流之间的转换、流和映射 map 和 flatMap、流的连接。

- 流的规约操作

- 流的查找与匹配

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,谢谢大家对我们的支持。

(0)

相关推荐

  • 详解Java8 Collect收集Stream的方法

    Collection, Collections, collect, Collector, Collectos Collection是Java集合的祖先接口. Collections是java.util包下的一个工具类,内涵各种处理集合的静态方法. java.util.stream.Stream#collect(java.util.stream.Collector<? super T,A,R>)是Stream的一个函数,负责收集流. java.util.stream.Collector 是一个收

  • Java8中利用stream对map集合进行过滤的方法

    前言 Stream 是用函数式编程方式在集合类上进行复杂操作的工具,其集成了Java 8中的众多新特性之一的聚合操作,开发者可以更容易地使用Lambda表达式,并且更方便地实现对集合的查找.遍历.过滤以及常见计算等. 最近公司在大张旗鼓的进行代码审核,从中也发现自己写代码的不好习惯.一次无意的点到了公司封装的对map集合过滤的方法,发现了stream.于是研究了一下.并对原有的代码再次结合Optional进行重构下 原有方法说明 主要处理过滤条件Map对象,过滤掉了null和空字符串 等操作 这

  • Java8如何构建一个Stream示例详解

    Stream初体验 Stream是Java8中操作集合的一个重要特性,我们先来看看Java里面是怎么定义Stream的: "A sequence of elements supporting sequential and parallel aggregate operations." 我们来解读一下上面的那句话: 1.Stream是元素的集合,这点让Stream看起来用些类似Iterator: 2.可以支持顺序和并行的对原Stream进行汇聚的操作. Stream的创建方式有很多种,除

  • 详解java8中的Stream数据流

    Stream是java8引入的一个重度使用lambda表达式的API.Stream使用一种类似用SQL语句从数据库查询数据的直观方式来提供一种对Java集合运算和表达的高阶抽象.直观意味着开发者在写代码时只需关注他们想要的结果是什么而无需关注实现结果的具体方式.这一章节中,我们将介绍为什么我们需要一种新的数据处理API.Collection和Stream的不同之处以及如何将StreamAPI应用到我们的编码中. 筛选重复的元素 Stream 接口支持 distinct 的方法, 它会返回一个元素

  • 初识Java8中的Stream

    lambda表达式是stream的基础,初学者建议先学习lambda表达式,http://www.jb51.net/article/121129.htm 1.初识stream 先来一个总纲: 东西就是这么多啦,stream是java8中加入的一个非常实用的功能,最初看时以为是io中的流(其实一点关系都没有),让我们先来看一个小例子感受一下: @Before public void init() { random = new Random(); stuList = new ArrayList<St

  • Java8新特性Stream流实例详解

    什么是Stream流? Stream流是数据渠道,用于操作数据源(集合.数组等)所生成的元素序列. Stream的优点:声明性,可复合,可并行.这三个特性使得stream操作更简洁,更灵活,更高效. Stream的操作有两个特点:可以多个操作链接起来运行,内部迭代. Stream可分为并行流与串行流,Stream API 可以声明性地通过 parallel() 与sequential() 在并行流与顺序流之间进行切换.串行流就不必再细说了,并行流主要是为了为了适应目前多核机器的时代,提高系统CP

  • Java8处理集合的优雅姿势之Stream

    前言 在Java中,集合和数组是我们经常会用到的数据结构,需要经常对他们做增.删.改.查.聚合.统计.过滤等操作.相比之下,关系型数据库中也同样有这些操作,但是在Java 8之前,集合和数组的处理并不是很便捷. 不过,这一问题在Java 8中得到了改善,Java 8 API添加了一个新的抽象称为流Stream,可以让你以一种声明的方式处理数据.本文就来介绍下如何使用Stream.特别说明一下,关于Stream的性能及原理不是本文的重点,如果大家感兴趣后面会出文章单独介绍. 1.Stream介绍

  • Java8中Stream使用的一个注意事项

    Stream简介 我们先来看看Java里面是怎么定义Stream的: A sequence of elements supporting sequential and parallel aggregate operations. 我们来解读一下上面的那句话: Stream是元素的集合,这点让Stream看起来用些类似Iterator: 可以支持顺序和并行的对原Stream进行汇聚的操作: 大家可以把Stream当成一个高级版本的Iterator.原始版本的Iterator,用户只能一个一个的遍历

  • java8中Stream的使用示例教程

    前言 Java8中提供了Stream对集合操作作出了极大的简化,学习了Stream之后,我们以后不用使用for循环就能对集合作出很好的操作. 本文将给大家详细介绍关于java8 Stream使用的相关内容,下面话不多说了,来一起看看详细的介绍吧 1. 原理 Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator. 原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作: 高级版本的 Stream,用户只要给出需

  • Java8中的Stream流式操作教程之王者归来

    前言 相对于Java8之前的Java的相关操作简直是天差地别,Java8 的流式操作的出现,也很大程度上改变了开发者对于Java的繁琐的操作的印象,从此,Java也走向了函数式编程的道路! 1 流的创建 1.1 流的创建方法 既然需要聊聊流的操作,那么,首先还是先看看怎么创建流. 创建流的方法有三种,分别是:Stream.of().Stream.iterate().Stream.generate(),然后,分别看一下这三个方法的声明. static <T> Stream<T> of

  • Java8中的Stream 流实践操作

    目录 1 前言 2 Stream 的分类 3 Stream 的操作 3.1 创建流的方式 3.2 流的中间操作 3.3 流的终止操作 总结 1 前言 Stream 是 java8 中处理集合的抽象概念,可以执行非常复杂的查询.过滤和映射数据等操作.Stream API 提供了一种高效的处理数据方式,Stream 对集合数据的操作可以说是非常的方便.Stream 是流,不是一种数据结构,也不会保存数据,只是一种数据处理方式,从一种数据组织结构到另外一种数据结构. 2 Stream 的分类 按照 S

  • JDK1.8新特性Stream流式操作的具体使用

    一. 前言 随着Java的发展,越来越多的企业开始使用JDK1.8 版本.JDK1.8 是自 JDK1.5之后最重要的版本,这个版本包含语言.编译器.库.工具.JVM等方面的十多个新特性.本次文章将着重学习Stream. Stream 是JDK1.8 中处理集合的关键抽象概念,Lambda 和 Stream 是JDK1.8新增的函数式编程最有亮点的特性了,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找.过滤和映射数据等操作.使用Stream API 对集合数据进行操作,就类似于使用SQ

  • 总结一下关于在Java8中使用stream流踩过的一些坑

    Java8的stream流 第一个坑: Collectors.toAsList()其实是new了一个list,在向里面赋值. 注意这里Collectors.toList()的写法,这里其实是底层new ArraryList().筛选的数据放到一个新的list.虽然标1处和标2处是同一个变量,但是内存地址是不一样啊.下面的逻辑时把hldrPolVOList中的某些元素删除.但是这个方法执行完后其实是没有删除里面元素的.原因就是这里的new ArraryList()更改了内存地址造成的. 测试: 解

  • Java8中Stream流式操作指南之入门篇

    目录 简介 正文 1. 流是什么 2. 老板,上栗子 3. 流的操作步骤 4. 流的特点 5. 流式操作和集合操作的区别: 总结 简介 流式操作也叫做函数式操作,是Java8新出的功能 流式操作主要用来处理数据(比如集合),就像泛型也大多用在集合中一样(看来集合这个小东西还是很关键的啊,哪哪都有它) 下面我们主要用例子来介绍下,流的基操(建议先看下lambda表达式篇,里面介绍的lambda表达式.函数式接口.方法引用等,下面会用到) 正文 1. 流是什么 流是一种以声明性的方式来处理数据的AP

  • java8新特性 stream流的方式遍历集合和数组操作

    前言: 在没有接触java8的时候,我们遍历一个集合都是用循环的方式,从第一条数据遍历到最后一条数据,现在思考一个问题,为什么要使用循环,因为要进行遍历,但是遍历不是唯一的方式,遍历是指每一个元素逐一进行处理(目的),而并不是从第一个到最后一个顺次处理的循环,前者是目的,后者是方式. 所以为了让遍历的方式更加优雅,出现了流(stream)! 1.流的目的在于强掉做什么 假设一个案例:将集合A根据条件1过滤为子集B,然后根据条件2过滤为子集C 在没有引入流之前我们的做法可能为: public cl

  • Go gRPC服务客户端流式RPC教程

    目录 前言 情景模拟:客户端大量数据上传到服务端 新建proto文件 创建Server端 创建Client端 思考 总结 前言 上一篇介绍了服务端流式RPC,客户端发送请求到服务器,拿到一个流去读取返回的消息序列. 客户端读取返回的流的数据.本篇将介绍客户端流式RPC. 客户端流式RPC:与服务端流式RPC相反,客户端不断的向服务端发送数据流,而在发送结束后,由服务端返回一个响应. 情景模拟:客户端大量数据上传到服务端 新建proto文件 新建client_stream.proto文件 1.定义

随机推荐