跟上时代学好 Stream 流 --Java8实战 学习

流简介

 流是什么: 从支持数据处理操作的源生成的元素序列.

元素序列

就像集合一样,流也提供了一个接口,可以访问特定元素类型的一组有序值.因为集合是数据结构.所以它的主要目的是以特定的时间/空间复杂度存储和访问元素(如ArrayList与LinkedList).但是流的目的在于表达式计算.比如 filter.sorted和map.集合讲的是数据,流讲的是计算.

源​​ 

  流会使用一个提供数据的源,如集合.数组或者输入/输出资源,请注意.从有序集合生成流时会保留原有的顺序.由列表生成的流,其元素顺序与列表一致.

数据处理操作

  流的数据处理功能支持类似于数据库的操作,以及函数式编程语言中的蝉蛹操作,如filter.map.reduce.find.match.sort等.流操作可以顺序执行(stream).也可以并行执行(parallelStream()) .

流操作特点

流水线

很多流的操作本身会返回一个流,这样多个操作就可以连接起来,形成一个大的流水线.入延迟和短路.流水线的操作可以看做对数据库进行数据库式查询.

内部迭代

与迭代器显示的集合不同,流的迭代操作是在背后进行的.

 

创建一个测试类

package com.clx.nettydemo.java8.stream;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @author clx
 *
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Dish {
  /**
   * 菜名
   */
  private  String name;
  /**
   * true
   */
  private  boolean vegetarian;
  /**
   * 热量
   */
  private  int calories;
  /**
   * 类型
   */
  private  Type type;


  public enum Type {
    MEAT, FISH, OTHER
  }

}

例子

package com.clx.nettydemo.java8;

import com.clx.nettydemo.java8.stream.Dish;

import java.lang.reflect.Member;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author Administrator
 */
public class Demo5 {
  public static void main(String[] args) {
    List<Dish> menu = Arrays.asList(
      new Dish("pork", false, 800, Dish.Type.MEAT),
      new Dish("beef", false, 700, Dish.Type.MEAT),
      new Dish("chicken", false, 400, Dish.Type.MEAT),
      new Dish("french fries", true, 530, Dish.Type.OTHER),
      new Dish("rice", true, 350, Dish.Type.OTHER),
      new Dish("season fruit", true, 120, Dish.Type.OTHER),
      new Dish("pizza", true, 550, Dish.Type.OTHER),
      new Dish("prawns", false, 300, Dish.Type.FISH),
      new Dish("salmon", false, 450, Dish.Type.FISH) );
      //从menu获得流(菜肴列表)
    List<String> collect = menu.stream()
      //过滤高热量的菜
      .filter((d) -> d.getCalories() > 300)
      //获取菜名
      .map(Dish::getName)
      //只选择头4条
      .limit(4)
      //将结果保存在另一个List中
      .collect(Collectors.toList());
    //[pork, beef, chicken]
    System.out.println(collect);
  }

}

我们先是对menu调用stream方法,由菜单得到一个流。数据源是菜肴列表(菜

单),它给流提供一个元素序列。接下来,对流应用一系列数据处理操作:filter、map、limit

和collect。除了collect之外,所有这些操作都会返回另一个流,这样它们就可以接成一条流

水线,于是就可以看作对源的一个查询。最后,collect操作开始处理流水线,并返回结果(它

和别的操作不一样,因为它返回的不是流,在这里是一个List)。在调用collect之前,没有任

何结果产生,实际上根本就没有从menu里选择元素。你可以这么理解:链中的方法调用都在排

队等待,直到调用collect。

image.png

filter——接受Lambda,从流中排除某些元素。在本例中,通过传递lambda d ->

d.getCalories() > 300,选择出热量超过300卡路里的菜肴。

 map——接受一个Lambda,将元素转换成其他形式或提取信息。在本例中,通过传递方

法引用Dish::getName,相当于Lambda d -> d.getName(),提取了每道菜的菜名。

 

 limit——截断流,使其元素不超过给定数量。

 collect——将流转换为其他形式。在本例中,流被转换为一个列表。它看起来有点儿

像变魔术,我们在第6章中会详细解释collect的工作原理。现在,你可以把collect看

作能够接受各种方案作为参数,并将流中的元素累积成为一个汇总结果的操作。这里的

toList()就是将流转换为列表的方案。

流与集合

Java现有的集合概念和新的流概念都提供了接口,来配合代表元素型有序值的数据接口,所谓有序,就是说我们一般般按顺序取用值,而不是随机取用的.

两者差异

集合与流之间的差异就在于什么时候进行计算.

集合

    是一个内存中的数据结构,它包含数据结构中目前所有的值--集合中的每个元素都得计算出来才能添加到集合中.(你可以往集合里添加东西或者删除东西,但不管什么时候,集合中的每个元素都是放在内存里的,元素都得先算出来才能成为集合的一部分.)

    在概念上固定的数据结构(你不能添加或删除元素),其元素则是按需计算的.这对编程有很大的好处

流只能遍历一次

 和迭代器类似,流只能遍历一次.遍历完之后,我们就说这个流已经被消费掉了.你可以从原始数据源哪里在获得一个新的流来重新遍历一遍,就像迭代器一样(这里假设它是集合之类的可重复的源,如果是I/O通道就没戏了).

例:

List<String> list = new arrayList<>();
Stream<String> stream = list.stream();
stream.forEach(System.out::println); //正常运行
stream.forEach(System.out::println); //报 java.langIllegalStateException流已被操作或关闭

集合和流的另一个关键区别在于它们的遍历数据的方式不同.

外部迭代与内部迭代

外部迭代

使用Collection接口需要用户去做迭代(比如用for-each)

List<String> names = new ArrayList<>(); 
//显示顺序迭代菜单 列表
for(Dish d: menu){ 
    //提取名称并将其添加到累加器
 names.add(d.getName()); 
}
//for-each还隐藏了迭代中的一些复杂性。for-each结构是一个语法糖,它背后的
//东西用Iterator对象表达出来更要丑陋得多

List<String> names = new ArrayList<>(); 
Iterator<String> iterator = menu.iterator(); 
while(iterator.hasNext()) { 
 Dish d = iterator.next(); 
 names.add(d.getName()); 
}

内部迭代

Streams库使用内部迭代--它帮你把迭代做了,还把得到的流值存放在某个地方,你只要给出一个函数说要干嘛就可以了

List<String> names = menu.stream()
    //用getName方法参数化map,提取菜单名
    .map(Dish::getName)
    //开始执行操作流水线,没有迭代
    .collect(toList());

image.png

 

流操作

例子:

List<String> collect = menu.stream()
      //过滤高热量的菜
      .filter((d) -> d.getCalories() > 300)
      //获取菜名
      .map(Dish::getName)
      //值获取3条 截断流,使其不超过指定的长度
      .limit(4)
      //将流转换成list
      .collect(toList());

可以分为两大类:

1.中间操作

filter map 和limit可以连接一条流水线(连接起来的流)

诸如fileter,sorted,distinct等中间操作会返回一个stream流,这让多个操作可以连接起来形成一个查询.重要的是,除非流水线上触发一个终端操作,否则中间操作不会执行任何处理--懒操作. 这是因为中间操作一般可以合起来用,在终端操作是一次性全部处理

所有中间操作会合并到同一次遍历中.(循环合并) . 相当于所有中间操作一起一遍一遍被遍历.是整体的.

2.终端操作

collect触发流水线执行并关闭它 (关闭流)

如图所示:

image.png

使用流

一个数据源(如集合) 来执行一个查询.

一个中间操作链,形成一条流的流水线.

一个终端操作,执行流水线.并生成结果

流的流水线背后的理念类似于构建起模式.在构建起模型中有一个调用链用来设置一套配置(对流来说这个就是一个中间操作链),接着是调用build方法(对流来说就是终端操作.)

操作

类型

返回类型

操作参数

函数描述符

filter

中间操作

Stream<T>

Predicate<T>

T->boolean

map

中间操作

Stream<R>

Function<T,R>

T->R

limit

中间操作

Stream<T>

 

 

sorted

中间操作

Stream<T>

Comparator<T>

(T,T)->int

distinct

中间操作

Stream<T>

 

 

forEach

终端操作

消费流中的每个元素并对其应用Lambda.这一操作返回Void

count

终端操作

返回流中元素的个数,这一操作返回long

collect

终端操作

把流归约成一个集合.比如List,Map,甚至是Integer

总结:

流是从支持数据处理操作的源生成的一系列元素

流利用内部迭代: 迭代通过 filter,map,limit,distint,sorted 等中间操作被抽象掉了

流操作有两类:中间操作和终端操作

filter和map等中间操作会返回一个Stream流,并可以连接起来一起,可以设置成一条流水线,但不会生成任何结果

foreach,count和collect等终端操作会返回一个非流的值,并处理流水线以返回结果.

流中的元素是按需计算的. 只有执行了才开始计算.懒延迟

 

记得点赞哦,谢谢啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值