0%

Stream流

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

2.Stream流

2.1 概述

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

2.2 常用操作

2.2.1 创建流

  • 使用 Stream 流 API 前要先把集合或者数组转换成流。

  • 单列结合集合对象.stream()

    1
    2
    List<Author> authors = getAuthors();
    Stream<Author> stream = authors.stream();
  • 数组Arrays.stream(数组)或者使用Stream.of(数组)来创建

    1
    2
    3
    Integer[] arr = {1,2,3,4,5};
    Stream<Integer> stream = Arrays.stream(arr);
    Stream<Integer> stream2= Stream.of(arr);
  • 双列集合:转换成单列集合后再创建

    1
    2
    3
    4
    5
    6
    Map<String,Integer> map = new HashMap<>();
    map.put("蜡笔小新",19);
    map.put("黑子",17);
    map.put("日向翔阳",16);
    //使用entrySet()方法将map集合->set集合
    Stream<Map.Entry<String,Integer>> stream = map.entrySet().stream();

2.2.2 中间操作

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

  • 例如:打印所有姓名长度大于 1 的作家的姓名

    1
    2
    3
    4
    List<Author> authors = getAuthors();
    authors.stream()
    .filter(author -> author.getName().length() > 1)
    .forEach(author -> System.out.println(author.getName()))
★map(转换或计算)
  • 可以对流中的元素进行转换或计算。(map可以调用多次:比如我先转换再计算,把对象转换成年龄,在计算年龄)

  • 例如:打印所有作家的姓名

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
     //(1)利用forEach直接打印
    authors.stream()
    .forEach(author -> System.out.println(author.getName()));
    //(2)利用map转换类型后再打印
    //lambda表达式
    authors.stream()
    .map(author -> author.getName()) // 流中元素的类型由Author转换成了String
    .forEach(s -> System.out.println(s));
    //匿名类的方式
    authors.stream()
    .map(new Function<Author, String>() {
    @Override
    public String apply(Author author) {
    return author.getName();
    }
    })
    .forEach(new Consumer<String>() {
    @Override
    public void accept(String s) {
    System.out.println(s);
    }
    });
  • 例如:将所有作家用 map 转换成年龄后,再将年龄 +10,并打印出来

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    //lambda的方式 
    authors.stream()
    .map(author -> author.getAge()) // 流中元素的类型由Author转换成了Integer
    .map(age -> age + 10) // 对流中的元素进行计算
    .forEach(age -> System.out.println(age));
    //匿名类的方式
    authors.stream()
    .map(new Function<Author, Integer>() {
    @Override
    public Integer apply(Author author) {
    return author.getAge();
    }
    })
    .map(new Function<Integer, Integer>() {
    @Override
    public Integer apply(Integer age) {
    return age + 10;
    }
    })
    .forEach(new Consumer<Integer>() {
    @Override
    public void accept(Integer age) {
    System.out.println(age);
    }
    });
distinct(去重)
  • 可以去除流中的重复元素。

  • 例如:打印所有作家的姓名,并且要求其中不能有重复元素

    1
    2
    3
    authors.stream()
    .distinct()
    .forEach(author -> System.out.println(author.getName()));
    • 注意:distinct 方法是依赖 Object 的 equals 方法来判断是否是相同对象的。所以需要注意重写 equals 方法。
sorted(排序)
  • 可以对流中的元素进行排序。

  • 例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    //(1)sorted()使用无参的方式,比较的对象的实体类(Author)需要实现Comparable接口
    //public class Author implements Comparable<Author>{}
    authors.stream()
    .distinct()
    .sorted()
    .forEach(author -> System.out.println(author));

    //(2)sorted()使用有参的方式,使用的是Comparator接口
    authors.stream()
    .distinct()
    .sorted((o1, o2) -> o2.getAge()-o1.getAge())
    .forEach(author -> System.out.println(author));
limit(限制流的长度)
  • 可以设置流的最大长度,超出的部分将被抛弃

  • 例如:对流中的元素按照年龄进行降序排序,并且要求不能有重复的元素,然后打印出年龄最大的两个作家的姓名。

    1
    2
    3
    4
    5
    authors.stream()
    .distinct()
    .sorted((o1, o2) -> o2.getAge()-o1.getAge())
    .limit(2)
    .forEach(author -> System.out.println(author.getName()));
skip(跳过)
  • 跳过流中的前n个元素,返回剩下的元素

  • 例如:打印除了年龄最大的作家外的其他作家,要求不能有重复元素,并且按照年龄降序排序。

    1
    2
    3
    4
    5
    authors.stream()
    .distinct()
    .sorted((o1, o2) -> o2.getAge()-o1.getAge())
    .skip(1)
    .forEach(author -> System.out.println(author.getName()));
★flatMap
  • map 只能把一个对象转换成另一个对象来作为流中的元素,而 flatMap 可以把一个对象转换成多个对象作为流中的元素。

  • 例一:打印所有书籍的名字。要求对重复的元素进行去重

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    //作者实体类中包含List<Book> books作品集合。flatMap的作用就是将一个authors.stream()转换成多个books.stream()的形式。
    authors.stream()
    .flatMap(new Function<Author, Stream<Book>>() {
    @Override
    public Stream<Book> apply(Author author) {
    return author.getBooks().stream();
    }
    })
    .distinct()
    .forEach(book -> System.out.println(book.getName()));
  • 例二:打印现有数据的所有分类。要求对分类进行去重。(不能出现这种格式:哲学,爱情)

    1
    2
    3
    4
    5
    6
    authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .distinct()
    .flatMap(book -> Arrays.stream(book.getCategory().split(",")))
    .distinct()
    .forEach(category -> System.out.println(category));

    在这里插入图片描述

2.2.3 终结操作

★forEach(遍历)
  • 对流中的元素进行遍历操作,我们通过传入的方法去指定对遍历到的元素具体进行什么操作

  • 例子:输出所有作家的名字

    1
    2
    3
    4
    authors.stream()
    .map(author -> author.getName())
    .distinct()
    .forEach(name -> System.out.println(name));
count
  • 可以用来获取当前流中元素的个数。

  • 例子:打印这些作家所出书籍的总数,注意删除重复元素。

    1
    2
    3
    4
    5
    long count = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .distinct()
    .count();
    System.out.println("这些作家的所出书籍的数目为:"+count);
max&min
  • 可以用来获取流中的最值

  • 例子:分别获取这些作家的所出书籍的最高分和最低分并打印

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    Optional<Integer> max = authors.stream()
    .flatMap(author -> author.getBooks().stream()) // 流中元素由Author转换为Book
    .map(book -> book.getScore()) // 流中元素由Book转换为Score
    .max((score1, score2) -> score1 - score2);
    System.out.println("最高分:"+max.get());

    Optional<Integer> min = authors.stream()
    .flatMap(author -> author.getBooks().stream()) // 流中元素由Author转换为Book
    .map(book -> book.getScore()) // 流中元素由Book转换为Score
    .min((score1, score2) -> score1 - score2);
    System.out.println("最低分:"+min.get());
★collect(流转换为集合)
  • 把当前流转化为一个集合。

  • 例子 1:获取一个存放所有作者名字的 List 集合

    1
    2
    3
    4
    List<String> nameList = authors.stream()
    .map(author -> author.getName())
    .collect(Collectors.toList());
    System.out.println(nameList) ;
  • 例子2:获取一个所有书名的 Set 集合

    1
    2
    3
    4
    5
    Set<String> bookNameList = authors.stream()
    .flatMap(author -> author.getBooks().stream())
    .map(book -> book.getName())
    .collect(Collectors.toSet());
    System.out.println(bookNameList);
  • 例子3:获取一个 Map 集合,map 的 key 为作者名,value 为 List

    1
    2
    3
    4
    5
    Map<String, List<Book>> authorMap = authors.stream()
    .distinct()
    .collect(Collectors.toMap(author -> author.getName(),
    author -> author.getBooks()));
    System.out.println(authorMap);
    • 关于 Collectors.toMap() 参数有三个的情况:使用 toMap() 函数之后,返回的就是一个Map了,自然会需要 key 和 value。
      • toMap() 的第一个参数就是用来传入 key 值的
      • 第二个参数就是用来传入 value 值的
      • 第三个参数用在 key 值冲突的情况下:如果新元素产生的 key 在 Map 中已经出现过了,第三个参数就会定义解决的办法。
查找与匹配
allMatch
  • 可以用来判断是否都符合匹配条件,结果为 boolean 类型。如果都符合结果为 true,否则结果为 false。

  • 例子:判断是否所有的作家都是成年人

    1
    2
    3
    boolean b = authors.stream()
    .allMatch(author -> author.getAge() >= 18);
    System.out.println(b);
anyMatch
  • 可以用来判断是否有任意符合匹配条件的元素,结果为 boolean 类型。

  • 例子:判断是否有年龄在 29 以上的作家

    1
    2
    3
    boolean b = authors.stream()
    .anyMatch(author -> author.getAge() > 29);
    System.out.println(b);
noneMatch
  • 可以判断流中的元素是否都不符合匹配条件。如果都不符合结果为 true,否则结果为 false。

  • 例子:判断作家是否都没有超过 100 岁

    1
    2
    3
    boolean b = authors.stream()
    .noneMatch(author -> author.getAge() > 100);
    System.out.println(b);
findAny
  • 获取流中的任意一个元素。该方法没有办法保证获取的一定是流中的第一个元素。

  • 例子:获取任意一个年龄大于 18 的作家,如果存在就输出他的名字

    1
    2
    3
    4
    Optional<Author> optionalAuthor = authors.stream()
    .filter(author -> author.getAge() > 18)
    .findAny();
    optionalAuthor.ifPresent(author -> System.out.println(author.getName()));
findFirst
  • 获取流中的第一个元素。

  • 例子:获取一个年龄最小的作家,并输出他的姓名。

    1
    2
    3
    4
     Optional<Author> first = authors.stream()
    .sorted((author1, author2) -> author1.getAge() - author2.getAge())
    .findFirst();
    first.ifPresent(author -> System.out.println(author.getName()));
★reduce(归并)
  • 对流中的数据按照你指定的计算方式计算出一个结果。(缩减操作)

    • reduce 的作用是把 stream 中的元素给组合起来,我们可以传入一个初始值,它会按照我们的计算方式将流中的元素和初始化值进行计算,计算结果再和后面的元素进行计算。

    • reduce 内部的计算方式如下:

      1
      2
      3
      4
      T result = identity;
      for(T element : this stream)
      result = accumulator.apply(result,element)
      return result;
      • 其中 identity 就是我们可以通过方法参数传入的初始值,accumulator 的 apply 具体进行什么计算是我们通过传入的具体方法来确定的。
  • 例子 1:使用 reduce 求所有作者年龄的和

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    //匿名内部类的方式
    Integer result = authors.stream()
    .distinct()
    .map(author -> author.getAge())
    .reduce(0, new BinaryOperator<Integer>() {
    @Override
    public Integer apply(Integer result, Integer element) {
    return result + element;
    }
    });
    System.out.println(result);

    //lambda表达式
    Integer result = authors.stream()
    .distinct()
    .map(author -> author.getAge())
    .reduce(0, (result1, element) -> result1 + element);
    System.out.println(result);
  • 例子2:使用 reduce 求所有作者中年龄的最大值。

    1
    2
    3
    4
    5
    6
    //注:求最大值,那么我们的初始化值就要是Integer.MIN_VALUE最小值。
    Integer reduce = authors.stream()
    .map(author -> author.getAge())
    .reduce(Integer.MIN_VALUE,
    (result, element) -> result > element ? result : element);
    System.out.println(reduce);

2.3 注意事项

  • 惰性求值(如果没有终结操作,中间操作是不会得到执行的),例如:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public static void main(String[] args) {
    List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 1);
    nums.stream()
    .map(num -> {
    num += 10;
    System.out.println(num);
    return num;
    }).distinct();
    }
    • 这个方法是没有输出的,因为没有终结操作,所以中间操作并不会执行,那么 System.out.println(num); 是不会运行的。

    而如果加上终结操作:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public static void main(String[] args) {
    List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 1);
    long count = nums.stream()
    .map(num -> {
    num += 10;
    System.out.println(num);
    return num;
    }).distinct()
    .count();
    System.out.println(count);
    }
    • 输出结果为:

      1
      2
      3
      4
      5
      6
      7
      11
      12
      13
      14
      15
      11
      5

      中间操作的 System.out.println(num); 运行了。

  • 流是一次性的(一旦一个流对象经过一个终结操作后,这个流就不能再被使用)

  • 不会影响原数据(我们在流中可以对数据做很多处理,但是正常情况下是不会影响原来集合中的元素的,这往往也是我们所期望的)

---------------The End---------------