当前位置:   article > 正文

Java8 stream流(详解)

java8 stream

一 : Stream流的介绍

Lambda表达式,基于Lambda所带来的函数式编程,又引入了一个全新的Stream概念,用于解决集合类库既有的弊端。

  1. stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果;

  1. stream不会改变数据源,通常情况下会产生一个新的集合;

  1. stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

  1. 对stream操作分为终端操作和中间操作,那么这两者分别代表什么呢?
    终端操作:会消费流,这种操作会产生一个结果的,如果一个流被消费过了,那它就不能被重用的。
    中间操作:中间操作会产生另一个流。因此中间操作可以用来创建执行一系列动作的管道。一个特别需要注意的点是:中间操作不是立即发生的。相反,当在中间操作创建的新流上执行完终端操作后,中间操作指定的操作才会发生。所以中间操作是延迟发生的,中间操作的延迟行为主要是让流API能够更加高效地执行。

  1. stream不可复用,对一个已经进行过终端操作的流再次调用,会抛出异常。

现有一个需求:

将list集合中姓张的元素过滤到一个新的集合中

然后将过滤出来的姓张的元素中,再过滤出来长度为3的元素,存储到一个新的集合中

1.用常规方法解决需求

  1. // 已知的知识来解决需求
  2. List<String> list1 = new ArrayList<>();
  3. list1.add("张老三");
  4. list1.add("张小三");
  5. list1.add("李四");
  6. list1.add("赵五");
  7. list1.add("张六");
  8. list1.add("王八");
  9. ArrayList<String> list2 = new ArrayList<>();
  10. // 1.将list集合中姓张的元素过滤到一个新的集合中
  11. for(String name : list1){
  12. if(name.startsWith("张")){
  13. list2.add(name);
  14. }
  15. }
  16. ArrayList list3 = new ArrayList();
  17. for (String name : list2) {
  18. if (name.length() == 3){
  19. list3.add(name);
  20. }
  21. }
  22. System.out.println(list3);

输出结果:

[张老三, 张小三]

2.用Stream流操作集合,获取流,过滤操作,打印输出

  1. list1.stream().filter((String name)->name.startsWith("张")).filter((String name)->name.length()==3).forEach((String name)->{
  2. System.out.println("符合条件的姓名:" + name);
  3. });

二 : 获取Stream流的方式

java.util.stream.Stream 是Java 8新加入的流接口。(并不是一个函数式接口

获取一个流非常简单,有以下几种常用的方式:

  • 所有的 Collection 集合都可以通过 stream 默认方法获取流(顺序流);

  • 所有的 Collection 集合都可以通过parallelStream获取并行流

  • Stream 接口的静态方法 of 可以获取数组对应的流。

  • Arrays的静态方法stream也可以获取流

根据Collection获取流

  1. public static void main(String[] args) {
  2. List<String> list = new ArrayList<>();
  3. Stream<String> stream1 = list.stream();
  4. Set<String> set = new HashSet<>();
  5. Stream<String> stream2 = set.stream();
  6. Vector<String> vector = new Vector<>();
  7. // ...
  8. }

根据Map获取流

  1. public static void main(String[] args) {
  2. Map<String, String> map = new HashMap<>();
  3. Stream<String> keyStream = map.keySet().stream();
  4. Stream<String> valueStream = map.values().stream();
  5. Stream<Map.Entry<String, String>> entryStream = map.entrySet().stream();
  6. }

根据数组获取流

如果使用的不是集合或映射而是数组,由于数组对象不可能添加默认方法,所以 Stream 接口中提供了静态方法of ,使用很简单:

  1. public static void main(String[] args) {
  2. //使用 Stream.of
  3. String[] array = { "张无忌", "张翠山", "张三丰", "张一元" };
  4. Stream<String> stream = Stream.of(array);
  5. //使用Arrays的静态方法
  6. Arrays.stream(array)
  7. }

三 : Stream流中的常用方法

这些方法可以被分成两种:

  • 延迟方法:返回值类型仍然是 Stream 接口自身类型的方法,因此支持链式调用。(除了终结方法外,其余方法均为延迟方法。)

  • 终结方法:返回值类型不再是 Stream 接口自身类型的方法,因此不再支持类似 StringBuilder 那样的链式调用。本小节中,终结方法包括 count 和 forEach 方法。

1.forEach(终结方法)

用于遍历的方法,参数传入一个函数式接口:Consumer

  1. public static void main(String[] args) {
  2. Stream<String> stream = Stream.of("张无忌", "张三丰", "周芷若");
  3. stream.forEach(name‐> System.out.println(name));
  4. }

2.过滤:filter

可以用于过滤

可以通过 filter 方法将一个流转换成另一个子集流。

  1. public static void main(String[] args) {
  2. //创建一个流
  3. Stream<String> stream = Stream.of("张三丰", "刘德华", "张国荣", "彭于晏", "纳什", "吴彦祖", "吴绮蓉");
  4. //对流中元素过滤,只要姓张的人
  5. Stream<String> stream2 = stream.filter(name -> {
  6. return name.startsWith("张");
  7. });
  8. //遍历过滤后的流
  9. stream2.forEach(name -> System.out.println(name));
  10. }

3.映射(转换):map

如果需要将流中的元素映射到另一个流中,可以使用 map 方法。

该接口需要一个 Function 函数式接口参数

此前我们已经学习过 java.util.stream.Function 函数式接口,其中唯一的抽象方法

这可以将一种T类型转换成为R类型,而这种转换的动作,就称为“映射"

map使用方法

  1. /**
  2. * stream流的map方法练习
  3. * map方法可以将流中的元素映射到另一个流中
  4. * map方法的参数是一个Function函数式接口
  5. */
  6. @Test
  7. public void test(){
  8. //创建一个流,里面是字符串类型的整数
  9. Stream<String> stream1 = Stream.of("2", "32", "2", "33", "2");
  10. //把stream1流中的整数全部转成int类型
  11. Stream<Integer> stream2 = stream1.map((String s) -> {
  12. return Integer.parseInt(s);
  13. });
  14. //遍历
  15. stream2.forEach((i)-> System.out.println(i));
  16. }

4.统计个数:count(终结方法)

正如旧集合 Collection 当中的 size 方法一样,流提供 count 方法来数一数其中的元素个数:

该方法返回一个long值代表元素个数(不再像旧集合那样是int值)。基本使用:

  1. public class Demo09StreamCount {
  2. public static void main(String[] args) {
  3. Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
  4. //筛选姓张的
  5. Stream<String> result = original.filter(s ‐> s.startsWith("张"));
  6. //输出个数
  7. System.out.println(result.count()); // 2
  8. }
  9. }

5.取用前几个(截取):limit

limit 方法可以对流进行截取,只取用前n个。

参数是一个long型,如果集合当前长度大于参数则进行截取;否则不进行操作。基本使用:

  1. public class Demo10StreamLimit {
  2. public static void main(String[] args) {
  3. Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
  4. //截取前两个
  5. Stream<String> result = original.limit(2);
  6. System.out.println(result.count()); // 2
  7. }
  8. }

6.跳过前几个元素:skip

如果希望跳过前几个元素,可以使用 skip 方法获取一个截取之后的新流:

如果流的当前长度大于n,则跳过前n个;否则将会得到一个长度为0的空流。基本使用

  1. public class Demo11StreamSkip {
  2. public static void main(String[] args) {
  3. Stream<String> original = Stream.of("张无忌", "张三丰", "周芷若");
  4. //跳过前两个,返回一个新的流
  5. Stream<String> result = original.skip(2);
  6. System.out.println(result.count()); // 1
  7. }
  8. }

7.组合(合并流):concat

如果有两个流,希望合并成为一个流,那么可以使用 Stream 接口的静态方法 concat :

  1. public class Demo12StreamConcat {
  2. public static void main(String[] args) {
  3. Stream<String> streamA = Stream.of("张无忌");
  4. Stream<String> streamB = Stream.of("张翠山");
  5. //合并成一个新的流
  6. Stream<String> result = Stream.concat(streamA, streamB);
  7. }
  8. }

8.筛选:distinct

去除流中重复的元素(使用hashcode和equals方法来对比)

9.映射(打开后再转换):flatMap

内部传入一个Function函数式接口,跟map的区别就是这个会把流中的元素打开,再组合成一个新的流

  1. // map和flatMap的练习
  2. public class StreamDemo {
  3. @Test
  4. public void test(){
  5. List<String> list = Arrays.asList("aa","bb","cc","dd");
  6. // 练习1 (map) 输出的全是大写
  7. list.stream().map(s -> s.toUpperCase()).forEach(System.out::println);
  8. System.out.println("----------");
  9. // 练习2(map)流里还有流,需要两个遍历才行看到里面内容
  10. Stream<Stream<Character>> streamStream = list.stream().map(StreamDemo::fromStringToStream);
  11. streamStream.forEach(s -> {
  12. s.forEach(System.out::println);
  13. });
  14. System.out.println("---------");
  15. // 练习3(flatMap)流里还有流,使用flatMap可以直接把里面的流打开,一次遍历就可以了
  16. Stream<Character> characterStream = list.stream().flatMap(StreamDemo::fromStringToStream);
  17. characterStream.forEach(System.out::println);
  18. }
  19. /**
  20. * 将字符串中的多个字符构成的集合转换为对应的stream
  21. * @param str
  22. * @return
  23. */
  24. public static Stream<Character> fromStringToStream(String str){
  25. ArrayList<Character> list = new ArrayList();
  26. // 将字符串转成字符数组,并遍历加入list集合
  27. for(Character c : str.toCharArray()){
  28. list.add(c);
  29. }
  30. // 返回list集合的stream流
  31. return list.stream();
  32. }
  33. }

10.自然排序:sorted

看下一条里的代码

11.定制排序:sorted(Comparator com)

  1. /**
  2. * 排序的练习
  3. */
  4. @Test
  5. public void test2(){
  6. List<Integer> integers = List.of(124, 2, 15, 12, 51, -5, 5);
  7. // 按照自然排序
  8. integers.stream().sorted().forEach(System.out::println);
  9. System.out.println("---------");
  10. List<Integer> integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
  11. // 定制排序(大到小),需要传入Comparator接口(如果流中的是引用类型,只能用定制排序)
  12. // 简写:integers2.stream().sorted((e1,e2) -> e2-e1).forEach(System.out::println);
  13. integers2.stream().sorted((e1,e2) -> {
  14. return e2-e1;
  15. }).forEach(System.out::println);
  16. }

12.检测匹配(终结方法):

返回一个Boolean值

是否全部匹配:allMatch

是否至少匹配一个:anyMatch

是否没有匹配的:noneMatch

  1. /**
  2. * 匹配的练习
  3. */
  4. @Test
  5. public void test3(){
  6. List<Integer> integers = List.of(124, 2, 15, 12, 51, -5, 5);
  7. // 判断是否全部大于5
  8. boolean b = integers.stream().allMatch(i -> i > 5);
  9. // 结束输出false
  10. System.out.println(b);
  11. System.out.println("-------");
  12. List<Integer> integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
  13. // 检测是否匹配至少一个元素
  14. boolean b1 = integers2.stream().anyMatch(i -> i > 5);
  15. // 输出true
  16. System.out.println(b1);
  17. System.out.println("-------");
  18. List<Integer> integers3 = List.of(124, 2, 15, 12, 51, -5, 5);
  19. // 检查是否没有匹配的元素
  20. boolean b2 = integers3.stream().noneMatch(i -> i > 1000);
  21. // 输出true,全部不匹配
  22. System.out.println(b2);
  23. }

13.查找元素(终结方法)

查找第一个元素:findFirst,返回Optional类型

查找其中一个元素:findAny,返回Optional类型

  1. public void test4(){
  2. List<Integer> integers = List.of(124, 2, 15, 12, 51, -5, 5);
  3. // 输出第一个元素
  4. Optional<Integer> first = integers.stream().findFirst();
  5. // 输出结果是Optional[124]
  6. System.out.println(first);
  7. System.out.println("-------------");
  8. List<Integer> integers2 = List.of(124, 2, 15, 12, 51, -5, 5);
  9. // 返回其中一个元素
  10. Optional<Integer> any = integers2.stream().findAny();
  11. System.out.println(any);
  12. }

14.查找最大最小值(终结方法)

max(comparator c)

min(comparator c)

  1. /**
  2. * 查找最大最小值
  3. */
  4. @Test
  5. public void test5(){
  6. List<Person> list = new ArrayList<>();
  7. list.add(new Person("马化腾",25,3000));
  8. list.add(new Person("李彦宏",27,2545));
  9. list.add(new Person("雷军",35,4515));
  10. list.add(new Person("马云",55,9877));
  11. // 查找年龄最大的人
  12. Optional<Person> max = list.stream().max((e1, e2) -> e1.getAge() - e2.getAge());
  13. // 返回马云,55岁年龄最大
  14. System.out.println(max.get());
  15. System.out.println("--------");

15.规约(终结方法)

reduce(T identity ,BinaryOperator) 第一个参数是初始值,第二个参数是一个函数式接口

reduce(BinaryOperator) 参数是一个函数式接口

  1. /**
  2. * 归约的练习
  3. */
  4. @Test
  5. public void test6(){
  6. List<Integer> integers = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
  7. // 求集合里数字的和(归约)reduce第一个参数是初始值。
  8. Integer sum = integers.stream().reduce(0, Integer::sum);
  9. System.out.println(sum);
  10. System.out.println("-------");
  11. List<Person> list = new ArrayList<>();
  12. list.add(new Person("马化腾",25,3000));
  13. list.add(new Person("李彦宏",27,2545));
  14. list.add(new Person("雷军",35,4515));
  15. list.add(new Person("马云",55,9877));
  16. // 求所有人的工资和(归约)
  17. // 不用方法引用写法:Optional<Integer> reduce = list.stream().map(person -> person.getSalary()).reduce((e1, e2) -> e1 + e2);
  18. Optional<Integer> reduce = list.stream().map(Person::getSalary).reduce(Integer::sum);
  19. // 输出Optional[19937]
  20. System.out.println(reduce);
  21. }

16.收集(终结方法)

collect(Collector c):将流转化为其他形式,接收一个Collector接口的实现

  1. /**
  2. * 收集的练习
  3. */
  4. @Test
  5. public void test7(){
  6. List<Person> list = new ArrayList<>();
  7. list.add(new Person("马化腾",25,3000));
  8. list.add(new Person("李彦宏",27,2545));
  9. list.add(new Person("雷军",35,4515));
  10. list.add(new Person("马云",55,9877));
  11. // 把年龄大于30岁的人,转成一个list集合
  12. List<Person> collect = list.stream().filter(person -> person.getAge() > 30).collect(Collectors.toList());
  13. // 遍历输出(输出雷军和马云)
  14. for (Person person : collect) {
  15. System.out.println(person);
  16. }
  17. System.out.println("----------");
  18. List<Person> list2 = new ArrayList<>();
  19. list2.add(new Person("马化腾",25,3000));
  20. list2.add(new Person("李彦宏",27,2545));
  21. list2.add(new Person("雷军",35,4515));
  22. list2.add(new Person("马云",55,9877));
  23. // 把姓马的人,转成Set集合
  24. Set<Person> set = list2.stream().filter(person -> person.getName().startsWith("马")).collect(Collectors.toSet());
  25. // 输出马云和马化腾
  26. set.forEach(System.out::println);
  27. }

17.迭代:iterate

可以使用Stream.iterate创建流值,即所谓的无限流。

  1. //Stream.iterate(initial value, next value)
  2. Stream.iterate(0, n -> n + 1)
  3. .limit(5)
  4. .forEach(x -> System.out.println(x));

18.查看:peek

peek接收的是一个Consumer函数,peek 操作会按照 Consumer 函数提供的逻辑去消费流中的每一个元素,同时有可能改变元素内部的一些属性

  1. @Test
  2. public void test1(){
  3. List<String> collect = Stream.of("one", "two", "three", "four")
  4. .filter(e -> e.length() > 3)
  5. .peek(e -> System.out.println("查看一下刚过滤出的值:" + e))
  6. .map(String::toUpperCase)
  7. .peek(e -> System.out.println("查看一下转大写之后的值:" + e))
  8. .collect(Collectors.toList());
  9. System.out.println("-----分割线-----");
  10. // 遍历过滤后的集合
  11. for (String s : collect) {
  12. System.out.println(s);
  13. }
  14. }

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/羊村懒王/article/detail/92824
推荐阅读
相关标签
  

闽ICP备14008679号