概述

Java8的Stream使用的是函数式编程模式,如同它的名字一样,它可以被用来对集合或数组进行链式流式的操作。可以更方便的让我们对集合或数组操作

准备工作

  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @EqualsAndHashCode//用于去重使用
  5. public class Author {
  6. private long id;
  7. private String name;
  8. private int age;
  9. private String intro;
  10. private List<Book> books;
  11. }
  1. @Data
  2. @NoArgsConstructor
  3. @AllArgsConstructor
  4. @EqualsAndHashCode
  5. public class Book {
  6. private long id;
  7. private String name;
  8. private String category;
  9. private int score;
  10. private String intro;
  11. }

入门案例

最后一定要一个终结操作来结束流操作,没有终结操作之前的操作不生效

  1. public static void main(String[] args) {
  2. List<Author> authors =getAuthors();//写了一个填充数据的方法
  3. authors.stream()
  4. //去重
  5. .distinct()
  6. //过滤,只要年龄小于20的
  7. .filter((author)->author.getAge()<20)
  8. //循环遍历数组,打印出来,这个是终结操作
  9. .forEach(author -> System.out.println(author.getName()));
  10. }

Debug Stream

想要查看流的链式过程,打个断点,然后调试,点击这个
image.png
image.png

常用操作

创建流

单列集合

集合对象.stream()

  1. List<Author> authors =getAuthors();
  2. Stream<Author> stream = authors.stream();

数组

Arrays.stream(数组)
Stream.of(数组)
两种方式来创建流

  1. int[] arr = {1,2,3,4};
  2. IntStream stream1 = Arrays.stream(arr);
  3. Stream<int[]> stream2 = Stream.of(arr);

双列集合

转成单列集合后再创建

  1. Map<String,Integer> map =new HashMap<>();
  2. Stream<Map.Entry<String, Integer>> stream3 = map.entrySet().stream();

中间操作

filter

可以对流中的元素进行条件过滤,符合条件的才能继续留在流中

  1. //只要年龄小于20的作者
  2. stream
  3. .filter(author -> author.getAge() < 20)
  4. .forEach(author-> System.out.println(author.getName()));

map

可以对流中的元素进行计算或者转换。比如把当前泛型类型映射成其他某种类型,也可以同类型映射,但是在映射的过程中进行一些计算操作,改变原有的数据。

  1. //打印所有作家的名字
  2. stream
  3. .map(author -> author.getName())
  4. .map(n -> "前缀:" + n)
  5. .forEach(name-> System.out.println(name));

distinct

可以去除流中重复的元素
注意:distinct方法是依赖Object的equals方法来判断是否是相同对象的。所以需要注意重写equals方法

  1. //打印所有作家的名字,不重复
  2. stream
  3. .distinct()
  4. .forEach(author-> System.out.println(author.getName()));

sorted

可以对流中的元素进行排序

如果调用空参的sorted()方法,需要流中的元素是实现了Comparable接口的

  1. //按年龄进行升降序排序
  2. stream
  3. //.sorted()
  4. .sorted((o1,o2) -> o1.getAge() - o2.getAge())
  5. .forEach(author-> System.out.println(author.getAge()));

limit

可以设置流的最大长度,选取几个?超出部分将被抛弃

  1. //选取前10个
  2. stream
  3. .limit(10)
  4. .forEach(author-> System.out.println(author.getName()));

skip

跳过流中的前n个元素,返回剩下的元素

  1. //跳过前5个
  2. stream
  3. .skip(5)
  4. .forEach(author-> System.out.println(author.getName()));

flatMap

map只能把一个对象转换成另一个对象来作为流中的元素。而flatMap可以把一个对象转换成多个对象作为流中的元素

  1. //将一个流中的集合元素再次转换成流进行操作
  2. stream
  3. .flatMap(author -> author.getBooks().stream())
  4. .distinct()
  5. .forEach(book -> System.out.println(book.getName()));
  1. //打印现有数据的所有分类。要求对分类进行去重。不能出现这种格式:哲学,爱情
  2. stream
  3. .flatMap(author -> author.getBooks().stream())
  4. .distinct()
  5. .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
  6. .distinct()
  7. .forEach(category-> System.out.println(category));

终结操作

forEach

对流中的元素进行遍历操作,我们通过传入的参数去指定对遍历到的元素进行什么具体操作。

  1. stream
  2. .distinct()
  3. .forEach(author-> System.out.println(author.getName()));

count

可以用来获取当前流中元素的个数

  1. //打印这些作家的所出书籍的数目,注意删除重复元素。
  2. List<Author> authors = getAuthors();
  3. long count = authors.stream()
  4. .flatMap(author -> author.getBooks().stream())
  5. .distinct()
  6. .count();
  7. System.out.println(count);

max&min

可以用来或者流中的最值

  1. //分别获取这些作家的所出书籍的最高分和最低分并打印。
  2. //Stream<Author> -> Stream<Book> ->Stream<Integer> ->求值
  3. //max和min的返回值是Optional类型,用get方法可以获取到值
  4. List<Author> authors = getAuthors();
  5. Optional<Integer> max = authors.stream()
  6. .flatMap(author -> author.getBooks().stream())
  7. .map(book -> book.getScore())
  8. .max((score1, score2) -> score1 - score2);
  9. Optional<Integer> min = authors.stream()
  10. .flatMap(author -> author.getBooks().stream())
  11. .map(book -> book.getScore())
  12. .min((score1, score2) -> score1 - score2);
  13. System.out.println(max.get());
  14. System.out.println(min.get());

collect

把当前流转换成一个集合

  1. //获取一个存放所有作者名字的List集合。
  2. List<Author> authors = getAuthors();
  3. List<String> nameList = authors.stream()
  4. .map(author -> author.getName())
  5. .collect(Collectors.toList());
  6. System.out.println(nameList);
  1. //获取一个所有书的Set集合。
  2. List<Author> authors = getAuthors();
  3. Set<Book> books = authors.stream()
  4. .flatMap(author -> author.getBooks().stream())
  5. .collect(Collectors.toSet());
  6. System.out.println(books);
  1. //获取一个Map集合,map的key为作者名,value为List<Book>
  2. List<Author> authors = getAuthors();
  3. Map<String, List<Book>> map = authors.stream()
  4. .distinct()
  5. .collect(Collectors.toMap(author -> author.getName(), author -> author.getBooks()));
  6. System.out.println(map);

查找与匹配

anyMatch

可以用来判断是否有任意符合匹配条件的元素,结果为boolean类型

  1. //判断是否有年龄在29以上的作家
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4. .anyMatch(author -> author.getAge() > 29);
  5. System.out.println(flag);

allMatch

可以用来判断是否都符合匹配条件,结果为boolean类型。如果都符合结果为true,否则结果为false。

  1. //判断是否所有的作家都是成年人
  2. List<Author> authors = getAuthors();
  3. boolean flag = authors.stream()
  4. .allMatch(author -> author.getAge() >= 18);
  5. System.out.println(flag);

noneMatch

可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为true,否则结果为false

  1. //判断作家是否都没有超过100岁的。
  2. List<Author> authors = getAuthors();
  3. boolean b = authors.stream()
  4. .noneMatch(author -> author.getAge() > 100);
  5. System.out.println(b);

findAny

获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素,随机的

  1. //获取任意一个年龄大于18的作家,如果存在就输出他的名字
  2. //findAny也是返回一个Optional对象,这是java为了防止空指针弄的一个对象,吧数据封装在里面
  3. List<Author> authors = getAuthors();
  4. Optional<Author> optionalAuthor = authors.stream()
  5. .filter(author -> author.getAge()>18)
  6. .findAny();
  7. //如果存在作者数据,就输出作者名字
  8. optionalAuthor.ifPresent(author -> System.out.println(author.getName()));

findFirst

获取流中的第一个元素

  1. //获取一个年龄最小的作家,并输出他的姓名。
  2. List<Author> authors = getAuthors();
  3. Optional<Author> first = authors.stream()
  4. .sorted((o1, o2) -> o1.getAge() - o2.getAge())
  5. .findFirst();
  6. first.ifPresent(author -> System.out.println(author.getName()));

reduce

对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)
reduce的作用是把stream中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式依次拿流中的元素和初始化值进行计算,计算结果再和后面的元素计算
reduce两个参数的重载形式内部的计算方式如下:

  1. //定义一个初始值,类型自定义
  2. T result = identity;
  3. //遍历流中的元素
  4. for (T element : this stream)
  5. //对流中的元素依次进行和初始值的计算
  6. result = accumulator.apply(result, element)
  7. //返回最终计算完的一个结果
  8. return result;

其中identity就是我们可以通过方法参数传入的初始值,accumulator的apply具体进行什么计算也是我们通过方法参数来确定的。
例子:

  1. //使用reduce求所有作者年龄的和
  2. List<Author> authors = getAuthors();
  3. Integer sum = authors.stream()
  4. .distinct()
  5. //把流从Author类型映射成Intger类型
  6. .map(author -> author.getAge())
  7. //初始值为0,result就是最终的值,element就是每次遍历的流中的元素
  8. .reduce(0, (result, element) -> result + element);
  9. System.out.println(sum);
  1. //使用reduce求所有作者中年龄的最大值
  2. List<Author> authors = getAuthors();
  3. Integer max = authors.stream()
  4. .map(author -> author.getAge())
  5. .reduce(Integer.MIN_VALUE, (result, element) -> result < element ? element : result);
  6. System.out.println(max);

reduce一个参数的重载形式内部的计算

  1. boolean foundAny = false;
  2. T result = null;
  3. for (T element : this stream) {
  4. //遍历第一个元素的时候,foundAny为false,满足这个if,进去后改成了true,result设置为第一个元素
  5. if (!foundAny) {
  6. foundAny = true;
  7. result = element;
  8. }
  9. //第二个元素及其之后,就执行相应的计算
  10. else
  11. result = accumulator.apply(result, element);
  12. }
  13. return foundAny ? Optional.of(result) : Optional.empty();

Stream流注意事项

  • 惰性求值(如果没有终结操作,没有中间操作是不会得到执行的)
  • 流是一次性的(一旦一个流对象经过一个终结操作后。这个流就不能再被使用)
  • 不会影响原数据(我们在流中可以多数据做很多处理。但是正常情况下是不会影响原来集合中的元素的。这往往也是我们期望的)

    高级操作

    基本数据类型转换

    我们之前用到的很多Stream的方法由于都使用了泛型。所以涉及到的参数和返回值都是引用数据类型。
    即使我们操作的是整数小数,但是实际用的都是他们的包装类。JDK5中引入的自动装箱和自动拆箱让我们在使用对应的包装类时就好像使用基本数据类型一样方便。但是你一定要知道装箱和拆箱肯定是要消耗时间的。虽然这个时间消耗很下。但是在大量的数据不断的重复装箱拆箱的时候,你就不能无视这个时间损耗了。
    所以为了让我们能够对这部分的时间消耗进行优化。Stream还提供了很多专门针对基本数据类型的方法。
    例如:mapToIntmapToLongmapToDoubleflatMapToIntflatMapToDouble等。

    1. private static void test27() {
    2. List<Author> authors = getAuthors();
    3. authors.stream()
    4. //会经历多次拆箱装箱
    5. .map(author -> author.getAge())
    6. .map(age -> age + 10)
    7. .filter(age->age>18)
    8. .map(age->age+2)
    9. .forEach(System.out::println);
    10. authors.stream()
    11. //只经历一次拆箱就可以了,之后使用的都是基本数据类型
    12. .mapToInt(author -> author.getAge())
    13. .map(age -> age + 10)
    14. .filter(age->age>18)
    15. .map(age->age+2)
    16. .forEach(System.out::println);
    17. }

    并行流

    当流中有大量元素时,我们可以使用并行流去提高操作的效率。其实并行流就是把任务分配给多个线程去完全。如果我们自己去用代码实现的话其实会非常的复杂,并且要求你对并发编程有足够的理解和认识。而如果我们使用Stream的话,我们只需要修改一个方法的调用就可以使用并行流来帮我们实现,从而提高效率。

    parallel

    parallel方法可以把串行流转换成并行流。

    1. private static void test28() {
    2. Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    3. Integer sum = stream.parallel()
    4. //中间操作,消费元素,不改变原有的数据
    5. .peek(new Consumer<Integer>() {
    6. @Override
    7. public void accept(Integer num) {
    8. System.out.println(num+Thread.currentThread().getName());
    9. }
    10. })
    11. .filter(num -> num > 5)
    12. .reduce((result, ele) -> result + ele)
    13. .get();
    14. System.out.println(sum);
    15. }

    parallelStream

    也可以通过parallelStream直接获取并行流对象

    1. List<Author> authors = getAuthors();
    2. authors.parallelStream()
    3. .map(author -> author.getAge())
    4. .map(age -> age + 10)
    5. .filter(age->age>18)
    6. .map(age->age+2)
    7. .forEach(System.out::println);