jdk8 Stream流

作者: cjsssss | 来源:发表于2018-01-02 10:30 被阅读0次

jdk8 Stream流


1.Stream API 简介

Stream API是Java 8中加入的一套新的API,主要用于处理集合操作,不过它的处理方式与传统的方式不同,
称为“数据流处理”。流(Stream)类似于关系数据库的查询操作,是一种声明式操作。比如要从数据库中获取所有年龄大于20岁的用户的名称,
并按照用户的创建时间进行排序,如果在sql中就会很容易完成,但是在java程序中,在jdk8以前可能要使用很多的if条件,但是在jdk8的stream
流中,我们可以这样:

List<String> userNames =
        users.stream()
        .filter(user -> user.getAge() > 20)
        .sorted(comparing(User::getAddDate))
        .map(User::getUserName)
        .collect(toList());

在Java中,集合是一种数据结构,或者说是一种容器,用于存放数据,流不是容器,它不关心数据的存放,只关注如何处理。


在遍历一个数组的时候,我们回采用for-each的方式,这种属于外部遍历,而流使用的是内部遍历的方式,也就是在内部帮你把逻辑给处理好了
可以用一下的方式:

// 外部
List<String> list = Arrays.asList("A", "B", "C", "D");
for (String str : list) {
    System.out.println(str);
}

// 内部
  list.stream().forEach(System.out::println);

其实在stream流的内部,Stream API将迭代操作封装到了内部,它会自动的选择最优的迭代方式,并且使用并行方式处理时,
将集合分成多段,每一段分别使用不同的线程处理,最后将处理结果合并输出。


需要注意的是,每个流的只能去遍历一次,如果对一个流遍历两次,会抛出java.lang.IllegalStateException异常

List<String> list = Arrays.asList("A", "B", "C", "D");
Stream<String> stream = list.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // 这里会抛出java.lang.IllegalStateException异常,因为流已经被关闭

流通常是有三个部分组成

1.数据源:流的获取,比如list.stream()方法;

2.中间处理:中间处理是对流元素的一系列处理。比如过滤filter,排序sorted,映射map

3.终端处理:终端处理会生成结果,结果可以是任何不是流值。生成List,可用collect(Collectors.toList()),生成Map可用collect(Collectors.toMap())
也可以不返回结果,如stream.forEach(System.out::println)就是将结果打印到控制台中,并没有返回。

2.使用流

  • filter():对元素进行过滤
  • map():将流的元素映射成另一个类型
  • distinct():去除流中重复的元素
  • sorted():对元素进行排序
  • forEach :对流中的每个元素执行某个操作
  • peek():与forEach()方法效果类似,不同的是,该方法会返回一个新的流,而forEach()无返回
  • limit():截取流中前面几个元素
  • skip():跳过流中前面几个元素
  • toArray():将流转换为数组
  • reduce():对流中的元素归约操作,将每个元素合起来形成一个新的值
  • collect():对流的汇总操作,比如输出成List集合
  • anyMatch():匹配流中的元素,类似的操作还有allMatch()和noneMatch()方法
  • findFirst():查找第一个元素,类似的还有findAny()方法
  • max():求最大值
  • min():求最小值
  • count():求总数

使用方法

2.1过滤和排序
Stream.of(1, 8, 5, 2, 1, 0, 9, 2, 0, 4, 8)
    .filter(n -> n > 2)     // 对元素过滤,保留大于2的元素
    .distinct()             // 去重,类似于SQL语句中的DISTINCT
    .skip(1)                // 跳过前面1个元素
    .limit(2)               // 返回开头2个元素,类似于SQL语句中的SELECT TOP
    .sorted()               // 对结果排序
    .forEach(System.out::println);

    //filter后剩下:8,5,9,4,8
    //去重后排序剩下:5,9
2.2查找和匹配

Stream中提供的查找方法有anyMatch()、allMatch()、noneMatch()、findFirst()、findAny()
这些方法被用来查找或匹配某些元素是否符合给定的条件:

  boolean  hasMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
                .anyMatch(s -> s.equals("Java"));
    // hasMatch:true
  boolean hasAllMatch = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .allMatch(s -> s.contains("#"));
        //hasAllMatch:false
   Optional<String> element = Stream.of("Java", "C#", "PHP", "C++", "Python")
        .filter(s -> s.contains("C"))
        // .findFirst()     // 查找第一个元素
        .findAny();         // 查找任意元素
        //element:Optional[C#]

实际上测试结果发现,findFirst()findAny()返回的都是第一个元素,两者之间到底有没什么区别呢,
通过查看javadoc描述,大致意思是findAny()是为了提高并行操作时的性能,所以在使用中如果没有特殊需求,都是
建议使用findAny()

2.3 归约

归约操作就是将流中的元素进行合并,形成一个新的值,常见的归约操作包括求和等运算,
求最大值或最小值。归约操作一般使用reduce()方法,
map()方法搭配使用,可以处理一些很复杂的归约操作。


      List<Book> books =Arrays.asList(
            new Book("Java编程思想", "Bruce Eckel", "机械工业出版社", 108.00D),
            new Book("Java 8实战", "Mario Fusco", "人民邮电出版社", 79.00D),
            new Book("MongoDB权威指南(第2版)", "Kristina Chodorow", "人民邮电出版社", 69.00D)
      );
      // 计算所有图书的总价
      Optional<Double> totalPrice = books.stream()
                                          .map(Book::getPrice)
                                          .reduce((n, m) -> n + m);
      // 价格最高的图书
      Optional<Book> expensive = books.stream().max(Comparator.comparing(Book::getPrice));
      // 价格最低的图书
      Optional<Book> cheapest = books.stream().min(Comparator.comparing(Book::getPrice));
      // 计算总数
      long count = books.stream().count();

在计算图书总价的时候首先使用map()方法得到所有图书价格的流,然后再使用reduce()方法进行归约计算。

3.数据收集

数据收集是流式数据处理的终端处理,与中间处理不同的是,终端处理会消耗流,
也就是说,终端处理之后,这个流就会被关闭,如果再进行中间处理,就会抛出异常。数据收集主要使用collect方法,
该方法也属于归约操作,像reduce()方法那样可以接收各种做法作为参数,将流中的元素累积成一个汇总结果,
具体的做法是通过定义新的Collector接口来定义的。

3.1规约和汇总

      // 求和
      long count = books.stream().collect(counting());

      // 价格最高的图书
      Optional<Book> expensive = books.stream().collect(maxBy(comparing(Book::getPrice)));

      // 价格最低的图书
      Optional<Book> cheapest = books.stream().collect(minBy(comparing(Book::getPrice)));

上面的代码假设你已经使用静态导入了CollectorsComparator两个类,
这样你就不用再去写Collectors.counting()Comparator.comparing()这样的代码了:

import static java.util.stream.Collectors.*;
import static java.util.Comparator.*;

Collectors类还包含一个joining()方法,该方法用于连接字符串:


     String str = Stream.of("A", "B", "C", "D").collect(joining(","));

3.2分组

和关系型数据库类似,流也提供了类似数据库的group by的特性,由Collectors.groupingBy()方法提供:

Map<String, List<Book>> booksGroup = books.stream().collect(groupingBy(Book::getPublisher));

上面的代码按照出版社对图书进行分组,分组的结果是一个Map对象
Mapkey值是出版社的名称,value值是每个出版社分组对应的集合。
分组方法groupingBy()接收一个Function接口作为参数,
上面的例子中我们使用了方法引用传递了出版社作为分组的依据
,但实际情况可能比这复杂,比如将价格在0-50之间的书籍分成一组,50-100之间的分成一组,
超过100的分成一组,这时候,我们可以直接使用Lambda表达式来表示这个分组逻辑:

// 获取流
      List<Book> books = Arrays.asList(
          new Book("Java编程思想", "Bruce Eckel", "机械工业出版社", 108.00D),
          new Book("Java 8实战", "Mario Fusco", "人民邮电出版社", 79.00D),
          new Book("MongoDB权威指南(第2版)", "Kristina Chodorow", "人民邮电出版社", 69.00D),
          new Book("MongoDB", "Kristina Chodorow", "人民邮电出版社", 50.00D),
          new Book("MongoDB", "Kristina Chodorow", "人民邮电出版社", 50.00D)
      );
Map<String, List<Book>> booksGroup = books
    .stream()
    .collect(groupingBy(book -> {
        if (book.getPrice() > 0 && book.getPrice() <= 50) {
            return "A";
        } else if (book.getPrice() > 50 && book.getPrice() <=100) {
            return "B";
        } else {
            return "C";
        }
    }));
//输出结果
{A=[Book{name='MongoDB', author='Kristina Chodorow', address='人民邮电出版社', price=50.0}, Book{name='MongoDB', author='Kristina Chodorow', address='人民邮电出版社', price=50.0}],
 B=[Book{name='Java 8实战', author='Mario Fusco', address='人民邮电出版社', price=79.0}, Book{name='MongoDB权威指南(第2版)', author='Kristina Chodorow', address='人民邮电出版社', price=69.0}],
 C=[Book{name='Java编程思想', author='Bruce Eckel', address='机械工业出版社', price=108.0}]}

groupingBy()方法还支持多级分组,
他有一个重载方法,除了接收一个Function类型的参数外,还接收一个Collector类型的参数:

Map<String, Map<String, List<Book>>> booksGroup = books.stream().collect(
        groupingBy(Book::getPublisher, groupingBy(book -> {
            if (book.getPrice() > 0 && book.getPrice() <= 50) {
                return "A";
            } else if (book.getPrice() > 50 && book.getPrice() <=100) {
                return "B";
            } else {
                return "C";
            }
        }))
);
//输出结果
{Mario Fusco={B=[Book{name='Java 8实战', author='Mario Fusco', address='人民邮电出版社', price=79.0}]},
 Bruce Eckel={C=[Book{name='Java编程思想', author='Bruce Eckel', address='机械工业出版社', price=108.0}]},
 Kristina Chodorow={A=[Book{name='MongoDB', author='Kristina Chodorow', address='人民邮电出版社', price=50.0},
                       Book{name='MongoDB', author='Kristina Chodorow', address='人民邮电出版社', price=50.0}],
                    B=[Book{name='MongoDB权威指南(第2版)', author='Kristina Chodorow', address='人民邮电出版社', price=69.0}]}}

groupingBy()的第二个参数可以是任意类型,只要是Collector接口的实例就可以,比如先分组,再统计数量:

Map<String, Long> countGroup = books.stream()
        .collect(groupingBy(Book::getPublisher, counting()));

还可以获取分组后每组价格最高的的图书:

Map<String, Book> expensiveGroup = books.stream()
               .collect(groupingBy(Book::getAuthor, collectingAndThen(
                       maxBy(comparingDouble(Book::getPrice)),
                       Optional::get
               )));
//输入结果
/*{Mario Fusco=Book{name='Java 8实战', author='Mario Fusco', address='人民邮电出版社', price=79.0},
 Bruce Eckel=Book{name='Java编程思想', author='Bruce Eckel', address='机械工业出版社', price=108.0},
 Kristina Chodorow=Book{name='MongoDB', author='Kristina Chodorow', address='人民邮电出版社', price=80.0}}*/

相关文章

  • Stream流的使用

    Stream流的使用 本篇主要讲解JDK8中 Stream流的使用, 包括如何 筛选 、切片、映射 、查找、匹配 ...

  • jdk8 Stream流

    jdk8 Stream流 1.Stream API 简介 Stream API是Java 8中加入的一套新的API...

  • Stream API

    1. Stream API 简介 Stream API是 JDK8 的新特性,这种风格将要处理的元素集合看作一种流...

  • JDK8 Stream性能测试,看看到底有多快

    JDK8 Stream 数据流效率分析 Stream 是Java SE 8类库中新增的关键抽象,它被定义于 jav...

  • Guli的技术点

    jdk8的新特性有那些: 项目使用到的stream流操作 常用方法以及示例:https://www.jianshu...

  • JDK8 Lambda & Stream使用笔记

    JDK8 Lambda & Stream使用笔记 简介 Lambda Stream Lambda语法 抽象语法结构...

  • [Java基础] Jdk8 - Steam

    [Java基础] Jdk8 - Steam 1. Stream 是什么? Stream 是对集合(Collecti...

  • JDK8新特性Stream流

    1. Stream流的出现原因 我们操作集合的时候有时候十分麻烦,如下 上面的集合操作数据的时候,每一个需求都需要...

  • JDK8stream将list转Map对象报错java.lang

    ​ JDK8有很多新特性,比如lambda表达式,函数式编程以及stream流的使用,这几个新特性,使用过之后就爱...

  • JDK8新特性之Stream流

    是什么是Stream流 java.util.stream.Stream Stream流和传统的IO流,它们都叫流,...

网友评论

    本文标题:jdk8 Stream流

    本文链接:https://www.haomeiwen.com/subject/fsrfnxtx.html