一、为什么引入Stream流

流是一系列与特定存储机制无关的元素——实际上,流并没有“存储”之说。使用流,无需迭代集合中的元素,就可以从管道提取和操作元素。这些管道通常被组合在一起,形成一系列对流进行操作的管道。

在大多数情况下,将对象存储在集合中是为了处理他们,因此你将会发现你将编程的主要焦点从集合转移到了流上,流的一个核心的好处是,它使得程序更加短小并且更易理解。当Lambda表达式和方法引用和流一起使用的时候会让人感觉自成一体。

二、如何使用Stream流

流操作的类型有三种:创建流修改流元素(中间操作 Intermediate Operations)消费流元素(终端操作 Terminal Operations)

创建Stream流

  1. 使用Arrays.stream()方法创建

    Integer[] arr = new Integer[]{1,2,3,4,5};
    Arrays.stream(arr).filter(num -> num > 3);
  2. 使用Stream.of ()方法创建

    Integer[] arr = new Integer[]{1,2,3,4,5};
    Stream.of(arr).filter(num -> num > 3);

    查看of()的源码中得知,该方法也是调用了Arrays.stream()方法实现的

    /**
    * Returns a sequential ordered stream whose elements are the specified values.
    *
    * @param <T> the type of stream elements
    * @param values the elements of the new stream
    * @return the new stream
    */
    @SafeVarargs
    @SuppressWarnings("varargs") // Creating a stream from an array is safe
    public static<T> Stream<T> of(T... values) {
    return Arrays.stream(values);
    }
  3. 使用Collection.stream()方法创建

    List<String> list = new ArrayList<>(1);
    list.stream().forEach(str -> System.out.println(str));
  4. 使用Stream.iterate()方法创建

    Stream.iterate(1, num -> num + 2).limit(10).forEach(num -> System.out.println(num));
  5. 使用Stream.generate()方法创建

    Stream.generate(() -> Arrays.asList(arr)).limit(1).forEach(num -> System.out.println(num));

修改流元素(中间操作 Intermediate Operations)

中间操作用于从一个流中获取对象,并将对象作为另一个流从后端输出,以连接到其他操作。

1、跟踪和调试

peek() 操作的目的是帮助调试,允许你无修改地查看流中的元素

// streams/Peeking.java
class Peeking {
public static void main(String[] args) throws Exception {
FileToWords.stream("Cheese.dat")
.skip(21)
.limit(4)
.map(w -> w + " ")
.peek(System.out::print)
.map(String::toUpperCase)
.peek(System.out::print)
.map(String::toLowerCase)
.forEach(System.out::print);
}
}

输出结果:

Well WELL well it IT it s S s so SO so

因为 peek() 符合无返回值的 Consumer 函数式接口,所以我们只能观察,无法使用不同的元素来替换流中的对象。

2、流元素排序

sorted()方法是需要遍历整个流的,并在产生任何元素之前对它进行排序。因为有可能排序后集合的第一个元素会在未排序集合的最后一位。

@Test
public void sortedTest() {
List<Integer> numList = Lists.newArrayList();
numList.add(8);
numList.add(2);
numList.add(6);
numList.add(9);
numList.add(1);
List<Integer> sortList = numList.stream().sorted(Integer::compareTo).collect(Collectors.toList());
System.out.println(sortList);
}

输出结果:

[1, 2, 6, 8, 9]
3、移除元素
  • distinct() 可用于消除流中的重复元素。相比创建一个 Set 集合,该方法的工作量要少得多。

    @Test
    public void distinctTest() {
    Stream.of(6, 8, 9, 6, 2, 8).distinct().forEach(i -> System.out.print(i + ", "));
    }

    输出结果:

    6, 8, 9, 2,
  • filter(Predicate):若元素传递给过滤函数产生的结果为true ,则过滤操作保留这些元素。

    @Test
    public void filterTest() {
    Stream.of(6, 9, 2, 8).filter(num -> num > 5).sorted().forEach(i -> System.out.print(i + ", "));
    }

    输出结果:

    6, 8, 9,
4、映射,应用函数到元素
  • map(Function):将函数操作应用在输入流的元素中,对一个流中的值进行某种形式的转换,并将返回值传递到输出流中

    @Test
    public void mapTest() {
    Stream.of("abc", "qw", "mnkh").map(String::length).forEach(n -> System.out.format("%d ", n));
    }

    输出结果:

    3 2 4
  • mapToInt(ToIntFunction):操作同上,但结果是 IntStream

    Stream.of("5", "7", "9").mapToInt(Integer::parseInt).forEach(n -> System.out.format("%d ", n));
  • mapToLong(ToLongFunction):操作同上,但结果是 LongStream

    Stream.of("17", "19", "23").mapToLong(Long::parseLong).forEach(n -> System.out.format("%d ", n));
  • mapToDouble(ToDoubleFunction):操作同上,但结果是 DoubleStream

    Stream.of("17", "1.9", ".23").mapToDouble(Double::parseDouble).forEach(n -> System.out.format("%f ", n));
  • flatMap() 做了两件事:将产生流的函数应用在每个元素上(与 map() 所做的相同),然后将每个流都扁平化为元素,因而最终产生的仅仅是元素。

    List<Integer> listA = Lists.newArrayList();
    listA.add(1);
    listA.add(6);
    List<Integer> listB = Lists.newArrayList();
    listB.add(10);
    listB.add(2);
    Map<String, List<Integer>> abMap = Maps.newHashMap();
    abMap.put("A", listA);
    abMap.put("B", listB);
    // 需获取A和B集合中大于5的元素
    abMap.values().stream().flatMap(num -> num.stream().filter(n -> n > 5)).collect(Collectors.toList())
    .forEach(System.out::println);

    输出结果:

    6
    10
  • flatMapToInt(Function):当 Function 产生 IntStream 时使用。

  • flatMapToLong(Function):当 Function 产生 LongStream 时使用。

  • flatMapToDouble(Function):当 Function 产生 DoubleStream 时使用。

5、集合流切片,可实现分页
  • limit(n)方法会返回一个包含n个元素的新的流(若总长小于n则返回原始流)。

  • skip(n)方法正好相反,它会丢弃掉前面的n个元素。

    // 查询第二页的数据
    Integer pageNumber = 2;
    Integer pageSize = 10;
    Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12).skip((pageNumber - 1) * pageSize).limit(pageSize)
    .forEach(System.out::println);

    输出结果:

    11
    12

消费流元素(终端操作 Terminal Operations)

终端操作总是我们在流管道中所做的最后一件事,该操作将会获取流的最终结果。

1、数组结果输出
  • toArray():将流转换成适当类型的数组。
  • toArray(generator):在特殊情况下,生成自定义类型的数组。
2、循环结果输出
  • forEach(Consumer)常见如 System.out::println 作为 Consumer 函数。

  • forEachOrdered(Consumer): 保证 forEach 在并行流处理时按照原始流顺序操作。

    Arrays.stream(new Random(45).ints(0, 1000).limit(100).toArray()).limit(10).parallel().forEachOrdered(n -> 						System.out.format("%d ", n));
3、collect收集结果
  • collect(Collector):使用 Collector 收集流元素到结果集合中。

  • collect(Supplier, BiConsumer, BiConsumer):同上,第一个参数 Supplier 创建了一个新的结果集合,第二个参数 BiConsumer 将下一个元素收集到结果集合中,第三个参数 BiConsumer 用于将两个结果集合合并起来。

    Collectorts类为我们提供了常用的收集类的各个工厂方法:

  1. 将一个流收集到一个List中可以这样用

    Lists.newArrayList().stream().collect(Collectors.toList());
  2. 收集到Set中可以这样用

    Lists.newArrayList().stream().collect(Collectors.toSet());
  3. 收集到Map中可以这样用

    Lists.newArrayList(new User("Johnson", "重庆")).stream().collect(Collectors.toMap(User::getName, User::getAddress));
  4. 收集到Set时,控制Set的类型可以这样用

    Lists.newArrayList().stream().collect(Collectors.toCollection(TreeSet::new));
  5. 将字流中的字符串连接并收集起来

    Lists.newArrayList().stream().collect(Collectors.joining(","));
  6. 各种聚合操作

    // 获取流中的总和,平均值,最大值,最小值,一次性收集流中的结果
    List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5);
    listA.stream().collect(Collectors.summingInt(Integer::intValue));
    listA.stream().collect(Collectors.averagingInt(Integer::intValue));
    listA.stream().collect(Collectors.maxBy(Integer::compareTo));
    listA.stream().collect(Collectors.minBy(Integer::compareTo));
    listA.stream().collect(Collectors.summarizingInt(Integer::intValue));
    // 分组分片,返回结果:{"重庆渝北":[{"address":"重庆渝北","name":"Johnson"},{"address":"重庆渝北","name":"Jack"}],"重庆江北":[{"address":"重庆江北","name":"Tom"}]}
    List<User> listB = Lists.newArrayList(new User("Johnson", "重庆渝北"), new User("Tom", "重庆江北"), new User("Jack", "重庆渝北"));
    System.out.println(JSON.toJSONString(listB.stream().collect(Collectors.groupingBy(User::getAddress))));
4、组合流中元素
  • reduce(BinaryOperator):使用 BinaryOperator 来组合所有流中的元素。因为流可能为空,其返回值为 Optional

    // 结果为15
    System.out.println(Stream.of(1, 2, 3, 4, 5).reduce((x, y) -> x + y).get());
  • reduce(identity, BinaryOperator):功能同上,但是使用 identity 作为其组合的初始值。因此如果流为空,identity 就是结果

    // 设置初始值为10则结果为25
    System.out.println(Stream.of(1, 2, 3, 4, 5).reduce(10, (x, y) -> x + y));
    // 集合流为空,则结果默认为初始值a
    List<String> list = Lists.newArrayList();
    System.out.println(list.stream().reduce("a", (x, y) -> x.length() > 1 ? x : y));
  • reduce(identity, BiFunction, BinaryOperator):在串行流(stream)中,该方法跟第二个方法一样,即第三个参数不会起作用。在并行流中,我们知道流被fork join出多个线程进行执行,此时每个线程的执行流程就跟第二个方法reduce(identity,BiFunction)一样,而第三个参数BinaryOperator函数,则是将每个线程的执行结果当成一个新的流,然后使用第一个方法reduce(BinaryOperator)流程进行规约

    // 第三个参数在并行流中起效,将每个线程的执行结果当成一个新的流
    List<Integer> listA = Lists.newArrayList(1, 2, 3, 4, 5);
    // 串行流运行结果:15
    System.out.println(listA.stream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
    // 并行流运行结果:120
    System.out.println(listA.parallelStream().reduce(0, (x, y) -> x + y, (i, j) -> i * j));
5、流中元素匹配
  • allMatch(Predicate) :如果流的每个元素提供给 Predicate 都返回 true ,结果返回为 true。在第一个 false 时,则停止执行计算。

    // 数组中第一个元素小于2,则停止匹配返回结果:flase
    System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 2));
    // 数组中所有元素都大于0,则停止匹配返回结果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).allMatch(n -> n > 0));
  • anyMatch(Predicate):如果流的任意一个元素提供给 Predicate 返回 true ,结果返回为 true。在第一个 true 是停止执行计算。

    // 数组中第三个元素大于2,则停止匹配返回结果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).anyMatch(n -> n > 2));
  • noneMatch(Predicate):如果流的每个元素提供给 Predicate 都返回 false 时,结果返回为 true。在第一个 true 时停止执行计算。

    // 数组中第三个元素大于2,则停止匹配返回结果:true
    System.out.println(Stream.of(1, 2, 3, 4, 5).noneMatch(n -> n > 2));
6、流中元素查找
  • findFirst():返回第一个流元素的 Optional,如果流为空返回 Optional.empty

    // 根据条件过滤后取第一个元素
    System.out.println(Stream.of(1, 2, 3, 4, 5).filter(n -> n > 2).findFirst().get());
  • findAny():返回含有任意流元素的 Optional,如果流为空返回 Optional.empty

    // 根据条件过滤后找到任何一个所匹配的元素,就返回,此方法在对流并行执行时效率高
    System.out.println(Stream.of(1, 2, 3, 4, 5).parallel().filter(n -> n > 2).findAny().get());
7、收集流信息
  • count():流中的元素个数。

  • max(Comparator):根据所传入的 Comparator 所决定的“最大”元素。

  • min(Comparator):根据所传入的 Comparator 所决定的“最小”元素。

  • average() :求取流元素平均值。

  • sum():对所有流元素进行求和。

  • summaryStatistics():生成有关此流元素的各种摘要数据。

    // 获取流中元素数量,返回结果:5
    System.out.println(Stream.of(1,2,3,4,5).count());
    // 获取流中最大值,返回结果:5
    System.out.println(Stream.of(1,2,3,4,5).max(Integer::compareTo).get());
    // 获取流中最小值,返回结果:1
    System.out.println(Stream.of(1,2,3,4,5).min(Integer::compareTo).get());
    // 获取流中元素平均值,返回结果:3.0
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).average().getAsDouble());
    // 获取流中各种摘要数据,返回结果:IntSummaryStatistics{count=5, sum=15, min=1, average=3.000000, max=5}
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).summaryStatistics());
    // 获取流中元素总和,返回结果:15
    System.out.println(Stream.of(1,2,3,4,5).mapToInt(Integer::intValue).sum());

三、总结

流式操作改变并极大地提升了 Java 语言的可编程性,并可能极大地阻止了 Java 编程人员向诸如 Scala 这种函数式语言的流转。

Java8新特性探索之Stream接口的更多相关文章

  1. Java8新特性探索之函数式接口

    一.为什么引入函数式接口 作为Java函数式编程爱好者,我们都知道方法引用和 Lambda 表达式都必须被赋值,同时赋值需要类型信息才能使编译器保证类型的正确性. 我们先看一个Lambda代码示例: ...

  2. 零基础学习java------21---------动态代理,java8新特性(lambda, stream,DateApi)

    1. 动态代理 在一个方法前后加内容,最简单直观的方法就是直接在代码上加内容(如数据库中的事务),但这样写不够灵活,并且代码可维护性差,所以就需要引入动态代理 1.1 静态代理实现 在讲动态代理之前, ...

  3. java8新特性学习:函数式接口

    本文概要 什么是函数式接口? 如何定义函数式接口? 常用的函数式接口 函数式接口语法注意事项 总结 1. 什么是函数式接口? 函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口 ...

  4. java8新特性学习:stream与lambda

    Streams api 对 Stream 的使用就是实现一个 filter-map-reduce 过程,产生一个最终结果,或者导致一个副作用(side effect). 流的操作类型分为两种: Int ...

  5. java8新特性Lambda和Stream

    Java8出来已经4年,但还是有很多人用上了jdk8,但并没用到里面的新东西,那不就等于没用?jdk8有许多的新特性,详细可看下面脑图 我只讲两个最重要的特性Lambda和Stram,配合起来用可以极 ...

  6. Java8新特性第2章(接口默认方法)

    在Java中一个接口一旦发布就已经被定型,除非我们能够一次性的更新所有该接口的实现,否者在接口的添加新方法将会破坏现有接口的实现.默认方法就是为了解决这一问题的,这样接口在发布之后依然能够继续演化. ...

  7. Java8新特性探索之新日期时间库

    一.为什么引入新的日期时间库 Java对日期,日历及时间的处理一直以来都饱受诟病,尤其是它决定将java.util.Date定义为可修改的以及将SimpleDateFormat实现成非线程安全的. 关 ...

  8. Java8新特性之三:Stream API

    Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式.Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找.过滤.筛选等操作 ...

  9. java8新特性- 默认方法 在接口中有具体的实现

    案例分析 在java8中在对list循环的时候,我们可以使用forEach这个方法对list进行遍历,具体代码如下demo所示 public static void main(String[] arg ...

随机推荐

  1. Shell学习(二)Shell变量

    一.Shell变量 变量的定义 例子: my_job="Learn Shell" PS:变量名和等号之间不能有空格!!! 命名只能使用英文字母,数字和下划线,首个字符不能以数字开头 ...

  2. 使用MATLAB 2019 App Design 工具设计一个 电子日记App

    使用MATLAB 2019 App Design 工具设计一个 电子日记App1.1 前言:由于信号与系统课程需要,因此下载了MATLAB软件,加之对新款的执着追求,通过一些渠道,下载了MATLAB ...

  3. Spring学习(二)--Spring的IOC

    1.依赖反转模式 依赖反转:高层次的模块不应该依赖于低层次的模块,两者都应该依赖于抽象接口.抽象接口不应该依赖于具体实现.而具体实现则应该依赖于抽象接口. 在面向对象编程领域中,依赖反转原则(Depe ...

  4. Python-面向网络编程-socket原理

    socket 整个计算机网络是由协议构成,想要通信必须遵守对应的协议,如Web中的http协议.传输协议TCP和UDP等等.在网络工程师的眼中,可能现在网络上的一切都是socket,一切皆socket ...

  5. shiro入门学习--使用MD5和salt进行加密|练气后期

    写在前面 在上一篇文章<Shiro入门学习---使用自定义Realm完成认证|练气中期>当中,我们学会了使用自定义Realm实现shiro数据源的切换,我们可以切换成从关系数据库如MySQ ...

  6. OpenCV计算机视觉学习(2)——图像算术运算 & 掩膜mask操作(数值计算,图像融合,边界填充)

    在OpenCV中我们经常会遇到一个名字:Mask(掩膜).很多函数都使用到它,那么这个Mask到底是什么呢,下面我们从图像基本运算开始,一步一步学习掩膜. 1,图像算术运算 图像的算术运算有很多种,比 ...

  7. Mat对象与像素操作 OpenCV C++

    Mat对象,分为两个部分,头部和数据部分 Mat对象拷贝之后是相互独立的 Mat对象有三种创建方法 CV_8UC1单通道,CV_8UC2双通道,CV_8UC3三通道,通道数 Scalar(0-255, ...

  8. Java死锁编码及定位分析的demo

    死锁 死锁是什么 大学课程中的四个要素: (1)互斥(2)不可抢占(3)循环等待(4)请求保持 也就是下图所描述 产生死锁的主要原因 (1)系统资源不足(2)进程运行推进的顺序不合适(3)资源分配不当 ...

  9. 用IPV6隧道连接IPV4孤岛

    hostA和hostB之间是IPV6连接的,但是之前的服务只能支持IPV4,兼容IPV6比较困难.所以用隧道实现hostA和hostB之间用IPV4连接. hostA如下: ip -6 addr ad ...

  10. keccak和sha3的区别

    keccak应用 在以太坊中,用keccak哈希算法来计算公钥的256位哈希,再截取这256位哈希的后160位哈希作为地址值. keccak和sha3的区别 sha3由keccak标准化而来,在很多场 ...