JDK8之Stream流

2023-10-26

1.集合处理数据的弊端

​ 当我们在需要对集合中的元素进行操作的时候,除了必需的添加,删除,获取外,最典型的操作就是集合遍历,

public class StreamTest01 {

    public static void main(String[] args) {
        // 定义一个List集合
        List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
        // 1.获取所有 姓张的信息
        List<String> list1 = new ArrayList<>();
        for (String s : list) {
            if(s.startsWith("张")){
                list1.add(s);
            }
        }

        // 2.获取名称长度为3的用户
        List<String> list2 = new ArrayList<>();
        for (String s : list1) {
            if(s.length() == 3){
                list2.add(s);
            }
        }

        // 3. 输出所有的用户信息
        for (String s : list2) {
            System.out.println(s);
        }
    }
}

上面的代码针对与我们不同的需求总是一次次的循环循环循环.这时我们希望有更加高效的处理方式,这时我们就可以通过JDK8中提供的Stream API来解决这个问题了。

Stream更加优雅的解决方案:

public class StreamTest02 {

    public static void main(String[] args) {
        // 定义一个List集合
        List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
        // 1.获取所有 姓张的信息
        // 2.获取名称长度为3的用户
        // 3. 输出所有的用户信息
        list.stream()
                .filter(s->s.startsWith("张"))
                .filter(s->s.length() == 3)
                .forEach(s->{
                    System.out.println(s);
                });
        System.out.println("----------");
        list.stream()
                .filter(s->s.startsWith("张"))
                .filter(s->s.length() == 3)
                .forEach(System.out::println);
    }
}

上面的SteamAPI代码的含义:获取流,过滤张,过滤长度,逐一打印。代码相比于上面的案例更加的简洁直观

2. Steam流式思想概述

注意:Stream和IO流(InputStream/OutputStream)没有任何关系,请暂时忘记对传统IO流的固有印象!
Stream流式思想类似于工厂车间的“生产流水线”,Stream流不是一种数据结构,不保存数据,而是对数据进行加工
处理。Stream可以看作是流水线上的一个工序。在流水线上,通过多个工序让一个原材料加工成一个商品。

在这里插入图片描述

在这里插入图片描述

Stream API能让我们快速完成许多复杂的操作,如筛选、切片、映射、查找、去除重复,统计,匹配和归约。

Stream概述

Java 8 是一个非常成功的版本,这个版本新增的Stream,配合同版本出现的 Lambda ,给我们操作集合(Collection)提供了极大的便利。

那么什么是Stream

Stream将要处理的元素集合看作一种流,在流的过程中,借助Stream API对流中的元素进行操作,比如:筛选、排序、聚合等。

Stream可以由数组或集合创建,对流的操作分为两种:

  1. 中间操作,每次返回一个新的流,可以有多个。
  2. 终端操作,每个流只能进行一次终端操作,终端操作结束后流无法再次使用。终端操作会产生一个新的集合或值。

另外,Stream有几个特性:

  1. stream不存储数据,而是按照特定的规则对数据进行计算,一般会输出结果。
  2. stream不会改变数据源,通常情况下会产生一个新的集合或一个值。
  3. stream具有延迟执行特性,只有调用终端操作时,中间操作才会执行。

在使用stream之前,先理解一个概念:Optional

Optional类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true,调用get()方法会返回该对象。
更详细说明请见:https://www.runoob.com/java/java8-optional-class.html

3. Stream流的获取方式

3.1 根据Collection获取

​ 首先,java.util.Collection 接口中加入了default方法 stream,也就是说Collection接口下的所有的实现都可以通过steam方法来获取Stream流。

通过 java.util.Collection.stream() 方法用集合创建流

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.stream();
        Set<String> set = new HashSet<>();
        set.stream();
        Vector vector = new Vector();
        vector.stream();
    }

List<String> list = Arrays.asList("a", "b", "c");
// 创建一个顺序流
Stream<String> stream = list.stream();
// 创建一个并行流
Stream<String> parallelStream = list.parallelStream();

​ 但是Map接口别没有实现Collection接口,那这时怎么办呢?这时我们可以根据Map获取对应的key value的集合。

    public static void main(String[] args) {
        Map<String,Object> map = new HashMap<>();
        Stream<String> stream = map.keySet().stream(); // key
        Stream<Object> stream1 = map.values().stream(); // value
        Stream<Map.Entry<String, Object>> stream2 = map.entrySet().stream(); // entry
    }

使用java.util.Arrays.stream(T[] array)方法用数组创建流

int[] array={1,3,5,6,8};
IntStream stream = Arrays.stream(array);

3.1 of/iterate/generate

​ 在实际开发中我们不可避免的还是会操作到数组中的数据,由于数组对象不可能添加默认方法,所有Stream接口中提供了静态方法of

public class StreamTest05 {

    public static void main(String[] args) {
        Stream<String> a1 = Stream.of("a1", "a2", "a3");
        String[] arr1 = {"aa","bb","cc"};
        Stream<String> arr11 = Stream.of(arr1);
        Integer[] arr2 = {1,2,3,4};
        Stream<Integer> arr21 = Stream.of(arr2);
        arr21.forEach(System.out::println);
        // 注意:基本数据类型的数组是不行的,会把它当成一个整体处理(一个对象)
        int[] arr3 = {1,2,3,4};
        Stream.of(arr3).forEach(System.out::println);//[I@214c265e
    }
}

使用Stream的静态方法:of()、iterate()、generate()

Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);

Stream<Integer> stream2 = Stream.iterate(0, (x) -> x + 3).limit(4);
stream2.forEach(System.out::println); // 0 3 6 9

Stream<Double> stream3 = Stream.generate(Math::random).limit(3);
stream3.forEach(System.out::println);

4.Stream常用方法介绍

在这里插入图片描述

Stream常用方法
Stream流模型的操作很丰富,这里介绍一些常用的API。这些方法可以被分成两种:

方法名 方法作用 返回值类型 方法种类
count 统计个数 long 终结
forEach 逐一处理 void 终结
filter 过滤 Stream 函数拼接
limit 取用前几个 Stream 函数拼接
skip 跳过前几个 Stream 函数拼接
map 映射 Stream 函数拼接
concat 组合 Stream 函数拼接

终结方法:返回值类型不再是 Stream 类型的方法,不再支持链式调用。本小节中,终结方法包括 count 和

forEach 方法。

非终结方法:返回值类型仍然是 Stream 类型的方法,支持链式调用。(除了终结方法外,其余方法均为非终结

方法。)

Stream注意事项(重要)

  1. Stream只能操作一次

  2. Stream方法返回的是新的流

  3. Stream不调用终结方法的话,中间的操作不会执行的

4.1 forEach

forEach用来遍历流中的数据的

void forEach(Consumer<? super T> action);

该方法接受一个Consumer接口,会将每一个流元素交给函数处理

    public static void main(String[] args) {
        Stream.of("a1", "a2", "a3").forEach(System.out::println);;
    }

4.2 count

Stream流中的count方法用来统计其中的元素个数的

long count();

该方法返回一个long值,代表元素的个数。

    public static void main(String[] args) {
        long count = Stream.of("a1", "a2", "a3").count();
        System.out.println(count);
    }

案例四:计算Integer集合中大于6的元素的个数。

import java.util.Arrays;
import java.util.List;

public class StreamTest {
 public static void main(String[] args) {
  List<Integer> list = Arrays.asList(7, 6, 4, 8, 2, 11, 9);

  long count = list.stream().filter(x -> x > 6).count();
  System.out.println("list中大于6的元素个数:" + count);
 }
}

输出结果:

list中大于6的元素个数:4

4.3 filter

filter方法的作用是用来过滤数据的。返回符合条件的数据

在这里插入图片描述

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

Stream<T> filter(Predicate<? super T> predicate);

该接口接收一个Predicate函数式接口参数作为筛选条件

    public static void main(String[] args) {
         Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                 .filter((s)->s.contains("a"))
                 .forEach(System.out::println);

    }

输出:

a1
a2
a3
aa

案例一:筛选出Integer集合中大于7的元素,并打印出来

public class StreamTest {
 public static void main(String[] args) {
  List<Integer> list = Arrays.asList(6, 7, 3, 8, 1, 2, 9);
  Stream<Integer> stream = list.stream();
  stream.filter(x -> x > 7).forEach(System.out::println);
 }
}

预期结果:

8 9

案例二:筛选员工中工资高于8000的人,并形成新的集合。 形成新集合依赖collect(收集),后文有详细介绍。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

  List<String> fiterList = personList.stream().filter(x -> x.getSalary() > 8000).map(Person::getName)
    .collect(Collectors.toList());
  System.out.print("高于8000的员工姓名:" + fiterList);
 }
}

运行结果:

高于8000的员工姓名:[Tom, Anni, Owen]

4.4 limit

在这里插入图片描述

limit方法可以对流进行截取处理,支取前n个数据,

Stream<T> limit(long maxSize);

参数是一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作:

    public static void main(String[] args) {
         Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                 .limit(3)
                 .forEach(System.out::println);

    }

输出:

a1
a2
a3

4.5 skip

在这里插入图片描述

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

   Stream<T> skip(long n);

操作:

    public static void main(String[] args) {
         Stream.of("a1", "a2", "a3","bb","cc","aa","dd")
                 .skip(3)
                 .forEach(System.out::println);

    }

输出:

bb
cc
aa
dd

4.6 map/flatMap

映射,可以将一个流的元素按照一定的映射规则映射到另一个流中。分为mapflatMap

  • map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。
  • flatMap:接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流。

在这里插入图片描述

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

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据

    public static void main(String[] args) {
         Stream.of("1", "2", "3","4","5","6","7")
                 //.map(msg->Integer.parseInt(msg))
                 .map(Integer::parseInt)
                 .forEach(System.out::println);

    }

案例一:英文字符串数组的元素全部改为大写。整数数组每个元素+3。

public class StreamTest {
 public static void main(String[] args) {
  String[] strArr = { "abcd", "bcdd", "defde", "fTr" };
  List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());

  List<Integer> intList = Arrays.asList(1, 3, 5, 7, 9, 11);
  List<Integer> intListNew = intList.stream().map(x -> x + 3).collect(Collectors.toList());

  System.out.println("每个元素大写:" + strList);
  System.out.println("每个元素+3:" + intListNew);
 }
}

输出结果:

每个元素大写:[ABCD, BCDD, DEFDE, FTR]
每个元素+3:[4, 6, 8, 10, 12, 14]

案例二:将员工的薪资全部增加1000。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

  // 不改变原来员工集合的方式
  List<Person> personListNew = personList.stream().map(person -> {
   Person personNew = new Person(person.getName(), 0, 0, null, null);
   personNew.setSalary(person.getSalary() + 10000);
   return personNew;
  }).collect(Collectors.toList());
  System.out.println("一次改动前:" + personList.get(0).getName() + "-->" + personList.get(0).getSalary());
  System.out.println("一次改动后:" + personListNew.get(0).getName() + "-->" + personListNew.get(0).getSalary());

  // 改变原来员工集合的方式
  List<Person> personListNew2 = personList.stream().map(person -> {
   person.setSalary(person.getSalary() + 10000);
   return person;
  }).collect(Collectors.toList());
  System.out.println("二次改动前:" + personList.get(0).getName() + "-->" + personListNew.get(0).getSalary());
  System.out.println("二次改动后:" + personListNew2.get(0).getName() + "-->" + personListNew.get(0).getSalary());
 }
}

输出结果:

一次改动前:Tom–>8900
一次改动后:Tom–>18900
二次改动前:Tom–>18900
二次改动后:Tom–>18900

案例三:将两个字符数组合并成一个新的字符数组。

public class StreamTest {
 public static void main(String[] args) {
  List<String> list = Arrays.asList("m,k,l,a", "1,3,5,7");
  List<String> listNew = list.stream().flatMap(s -> {
   // 将每个元素转换成一个stream
   String[] split = s.split(",");
   Stream<String> s2 = Arrays.stream(split);
   return s2;
  }).collect(Collectors.toList());

  System.out.println("处理前的集合:" + list);
  System.out.println("处理后的集合:" + listNew);
 }
}

输出结果:

处理前的集合:[m-k-l-a, 1-3-5]
处理后的集合:[m, k, l, a, 1, 3, 5]

4.7 sorted

sorted,中间操作。有两种排序:

  • sorted():自然排序,流中元素需实现Comparable接口
  • sorted(Comparator com):Comparator排序器自定义排序
    Stream<T> sorted();

在使用的时候可以根据自然规则排序,也可以通过比较强来指定对应的排序规则

    public static void main(String[] args) {
         Stream.of("1", "3", "2","4","0","9","7")
                 //.map(msg->Integer.parseInt(msg))
                 .map(Integer::parseInt)
                 //.sorted() // 根据数据的自然顺序排序
                 .sorted((o1,o2)->o2-o1) // 根据比较强指定排序规则
                 .forEach(System.out::println);

    }

案例:将员工按工资由高到低(工资一样则按年龄由大到小)排序

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();

  personList.add(new Person("Sherry", 9000, 24, "female", "New York"));
  personList.add(new Person("Tom", 8900, 22, "male", "Washington"));
  personList.add(new Person("Jack", 9000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 8800, 26, "male", "New York"));
  personList.add(new Person("Alisa", 9000, 26, "female", "New York"));

	resList.stream().sorted(Comparator.comparing(AccessDataBo::getMakeTime)).collect(Collectors.toList());

  // 按工资增序排序
  List<String> newList = personList.stream().sorted(Comparator.comparing(Person::getSalary)).map(Person::getName)
    .collect(Collectors.toList());
  // 按工资倒序排序
  List<String> newList2 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
    .map(Person::getName).collect(Collectors.toList());
  // 先按工资再按年龄自然排序(从小到大)
  List<String> newList3 = personList.stream().sorted(Comparator.comparing(Person::getSalary).reversed())
    .map(Person::getName).collect(Collectors.toList());
  // 先按工资再按年龄自定义排序(从大到小)
  List<String> newList4 = personList.stream().sorted((p1, p2) -> {
   if (p1.getSalary() == p2.getSalary()) {
    return p2.getAge() - p1.getAge();
   } else {
    return p2.getSalary() - p1.getSalary();
   }
  }).map(Person::getName).collect(Collectors.toList());

  System.out.println("按工资自然排序:" + newList);
  System.out.println("按工资降序排序:" + newList2);
  System.out.println("先按工资再按年龄自然排序:" + newList3);
  System.out.println("先按工资再按年龄自定义降序排序:" + newList4);
 }
}

示例1:从小到大,正序排序

List<Student> sList = studentList.stream().sorted(Comparator.comparing(Student::getId)).collect(Collectors.toList());

示例2:倒序

List<Student> sList = studentList.stream().sorted(Comparator.comparing(Student::getId).reversed()).collect(Collectors.toList());

运行结果:

按工资自然排序:[Lily, Tom, Sherry, Jack, Alisa]
按工资降序排序:[Sherry, Jack, Alisa,Tom, Lily]
先按工资再按年龄自然排序:[Sherry, Jack, Alisa, Tom, Lily]
先按工资再按年龄自定义降序排序:[Alisa, Jack, Sherry, Tom, Lily]

4.8 提取/组合(distinct)

流也可以进行合并、去重、限制、跳过等操作。

在这里插入图片描述
在这里插入图片描述

如果要去掉重复数据,可以使用distinct方法:

    Stream<T> distinct();

使用:

    public static void main(String[] args) {
         Stream.of("1", "3", "3","4","0","1","7")
                 //.map(msg->Integer.parseInt(msg))
                 .map(Integer::parseInt)
                 //.sorted() // 根据数据的自然顺序排序
                 .sorted((o1,o2)->o2-o1) // 根据比较强指定排序规则
                 .distinct() // 去掉重复的记录
                 .forEach(System.out::println);
        System.out.println("--------");
        Stream.of(
                new Person("张三",18)
                ,new Person("李四",22)
                ,new Person("张三",18)
        ).distinct()
                .forEach(System.out::println);

    }

​ Stream流中的distinct方法对于基本数据类型是可以直接出重的,但是对于自定义类型,我们是需要重写hashCode和equals方法来移除重复元素

public class StreamTest {
 public static void main(String[] args) {
  String[] arr1 = { "a", "b", "c", "d" };
  String[] arr2 = { "d", "e", "f", "g" };

  Stream<String> stream1 = Stream.of(arr1);
  Stream<String> stream2 = Stream.of(arr2);
  // concat:合并两个流 distinct:去重
  List<String> newList = Stream.concat(stream1, stream2).distinct().collect(Collectors.toList());
  // limit:限制从流中获得前n个数据
  List<Integer> collect = Stream.iterate(1, x -> x + 2).limit(10).collect(Collectors.toList());
  // skip:跳过前n个数据
  List<Integer> collect2 = Stream.iterate(1, x -> x + 2).skip(1).limit(5).collect(Collectors.toList());

  System.out.println("流合并:" + newList);
  System.out.println("limit:" + collect);
  System.out.println("skip:" + collect2);
 }
}

运行结果:

流合并:[a, b, c, d, e, f, g]
limit:[1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
skip:[3, 5, 7,

4.9 match

如果需要判断数据是否匹配指定的条件,可以使用match相关的方法

boolean anyMatch(Predicate<? super T> predicate); // 元素是否有任意一个满足条件
boolean allMatch(Predicate<? super T> predicate); // 元素是否都满足条件
boolean noneMatch(Predicate<? super T> predicate); // 元素是否都不满足条件

使用

    public static void main(String[] args) {
        boolean b = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                //.allMatch(s -> s > 0)
                //.anyMatch(s -> s >4)
                .noneMatch(s -> s > 4)
                ;
        System.out.println(b);
    }

注意match是一个终结方法

4.10 find

如果我们需要找到某些数据,可以使用find方法来实现

    Optional<T> findFirst();
    Optional<T> findAny();

在这里插入图片描述

使用:

    public static void main(String[] args) {
		// 匹配第一个
        Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7").findFirst();
        System.out.println(first.get());
		// 匹配任意(适用于并行流)
        Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7").findAny();
        System.out.println(any.get());
        // 是否包含符合特定条件的元素
        boolean anyMatch = list.stream().anyMatch(x -> x < 6);
    }

4.11 max和min

在这里插入图片描述

如果我们想要获取最大值和最小值,那么可以使用max和min方法

Optional<T> min(Comparator<? super T> comparator);
Optional<T> max(Comparator<? super T> comparator);

使用

    public static void main(String[] args) {
        Optional<Integer> max = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                .max((o1,o2)->o1-o2);
        System.out.println(max.get());

        Optional<Integer> min = Stream.of("1", "3", "3", "4", "5", "1", "7")
                .map(Integer::parseInt)
                .min((o1,o2)->o1-o2);
        System.out.println(min.get());
    }

案例一:获取String集合中最长的元素。

public class StreamTest {
 public static void main(String[] args) {
  List<String> list = Arrays.asList("adnm", "admmt", "pot", "xbangd", "weoujgsd");

  Optional<String> max = list.stream().max(Comparator.comparing(String::length));
  System.out.println("最长的字符串:" + max.get());
 }
}

输出结果:

最长的字符串:weoujgsd

案例二:获取Integer集合中的最大值。

public class StreamTest {
 public static void main(String[] args) {
  List<Integer> list = Arrays.asList(7, 6, 9, 4, 11, 6);

  // 自然排序
  Optional<Integer> max = list.stream().max(Integer::compareTo);
  // 自定义排序
  Optional<Integer> max2 = list.stream().max(new Comparator<Integer>() {
   @Override
   public int compare(Integer o1, Integer o2) {
    return o1.compareTo(o2);
   }
  });
  System.out.println("自然排序的最大值:" + max.get());
  System.out.println("自定义排序的最大值:" + max2.get());
 }
}

输出结果:

自然排序的最大值:11
自定义排序的最大值:11

案例三:获取员工工资最高的人。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

  Optional<Person> max = personList.stream().max(Comparator.comparingInt(Person::getSalary));
  System.out.println("员工工资最大值:" + max.get().getSalary());
 }
}

输出结果:

员工工资最大值:9500

4.12 reduce方法

归约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积和求最值操作。
在这里插入图片描述

如果需要将所有数据归纳得到一个数据,可以使用reduce方法

T reduce(T identity, BinaryOperator<T> accumulator);

使用:

    public static void main(String[] args) {
        Integer sum = Stream.of(4, 5, 3, 9)
                // identity默认值
                // 第一次的时候会将默认值赋值给x
                // 之后每次会将 上一次的操作结果赋值给x y就是每次从数据中获取的元素
                .reduce(0, (x, y) -> {
                    System.out.println("x="+x+",y="+y);
                    return x + y;
                });
        System.out.println(sum);
        // 获取 最大值
        Integer max = Stream.of(4, 5, 3, 9)
                .reduce(0, (x, y) -> {
                    return x > y ? x : y;
                });
        System.out.println(max);
    }

案例一:求Integer集合的元素之和、乘积和最大值。

public class StreamTest {
 public static void main(String[] args) {
  List<Integer> list = Arrays.asList(1, 3, 2, 8, 11, 4);
  // 求和方式1
  Optional<Integer> sum = list.stream().reduce((x, y) -> x + y);
  // 求和方式2
  Optional<Integer> sum2 = list.stream().reduce(Integer::sum);
  // 求和方式3
  Integer sum3 = list.stream().reduce(0, Integer::sum);
  
  // 求乘积
  Optional<Integer> product = list.stream().reduce((x, y) -> x * y);

  // 求最大值方式1
  Optional<Integer> max = list.stream().reduce((x, y) -> x > y ? x : y);
  // 求最大值写法2
  Integer max2 = list.stream().reduce(1, Integer::max);

  System.out.println("list求和:" + sum.get() + "," + sum2.get() + "," + sum3);
  System.out.println("list求积:" + product.get());
  System.out.println("list求和:" + max.get() + "," + max2);
 }
}

输出结果:

list求和:29,29,29
list求积:2112
list求和:11,11

案例二:求所有员工的工资之和和最高工资。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));
  personList.add(new Person("Anni", 8200, 24, "female", "New York"));
  personList.add(new Person("Owen", 9500, 25, "male", "New York"));
  personList.add(new Person("Alisa", 7900, 26, "female", "New York"));

  // 求工资之和方式1:
  Optional<Integer> sumSalary = personList.stream().map(Person::getSalary).reduce(Integer::sum);
  // 求工资之和方式2:
  Integer sumSalary2 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(),
    (sum1, sum2) -> sum1 + sum2);
  // 求工资之和方式3:
  Integer sumSalary3 = personList.stream().reduce(0, (sum, p) -> sum += p.getSalary(), Integer::sum);

  // 求最高工资方式1:
  Integer maxSalary = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
    Integer::max);
  // 求最高工资方式2:
  Integer maxSalary2 = personList.stream().reduce(0, (max, p) -> max > p.getSalary() ? max : p.getSalary(),
    (max1, max2) -> max1 > max2 ? max1 : max2);

  System.out.println("工资之和:" + sumSalary.get() + "," + sumSalary2 + "," + sumSalary3);
  System.out.println("最高工资:" + maxSalary + "," + maxSalary2);
 }
}

输出结果:

工资之和:49300,49300,49300
最高工资:9500,9500

4.13 map和reduce的组合

​ 在实际开发中我们经常会将map和reduce一块来使用

    public static void main(String[] args) {
        // 1.求出所有年龄的总和
        Integer sumAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).map(Person::getAge) // 实现数据类型的转换
                .reduce(0, Integer::sum);
        System.out.println(sumAge);

        // 2.求出所有年龄中的最大值
        Integer maxAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).map(Person::getAge) // 实现数据类型的转换,符合reduce对数据的要求
                .reduce(0, Math::max); // reduce实现数据的处理
        System.out.println(maxAge);
        // 3.统计 字符 a 出现的次数
        Integer count = Stream.of("a", "b", "c", "d", "a", "c", "a")
                .map(ch -> "a".equals(ch) ? 1 : 0)
                .reduce(0, Integer::sum);
        System.out.println(count);
    }

// List对象中某一属性 重新生成List
List<User>list=new ArrayList<>();
    User user1=new User();
    user1.setId(1);
    user1.setName("李四");
    User user2=new User();
    user2.setId(2);
    user2.setName("王五");
    list.add(user1);
    list.add(user2);

    List<Integer> collect = list.stream().map(User::getId).collect(Collectors.toList());
    System.out.println(collect);

//java8实现数组转字符串(以特定字符拼接)的方法
        List list = new ArrayList();
        list.add("a");
        list.add("b");
        list.add("c");
 
        String str = (String) list.stream().collect(Collectors.joining(","));
        System.out.println(str);
 
        //如果是整形
        list.stream().map(String::valueOf).collect(Collectors.joining(","));

输出结果

87
22
3

4.14 mapToInt

IntStream mapToInt(ToIntFunction<? super T> mapper);
LongStream mapToLong(ToLongFunction<? super T> mapper);
DoubleStream mapToDouble(ToDoubleFunction<? super T> mapper); 

如果需要将Stream中的Integer类型转换成int类型,可以使用mapToInt方法来实现

在这里插入图片描述

使用

    public static void main(String[] args) {
        // Integer占用的内存比int多很多,在Stream流操作中会自动装修和拆箱操作
        Integer arr[] = {1,2,3,5,6,8};
        Stream.of(arr)
                .filter(i->i>0) //做装修和拆箱操作
                .forEach(System.out::println);
        System.out.println("---------");
        // 为了提高程序代码的效率,我们可以先将流中Integer数据转换为int数据,然后再操作
        IntStream intStream = Stream.of(arr)
                .mapToInt(Integer::intValue);
        intStream.filter(i->i>3)
                .forEach(System.out::println);

    }

4.15 concat

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

    public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
        Objects.requireNonNull(a);
        Objects.requireNonNull(b);

        @SuppressWarnings("unchecked")
        Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
                (Spliterator<T>) a.spliterator(), (Spliterator<T>) b.spliterator());
        Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
        return stream.onClose(Streams.composedClose(a, b));
    }

使用:

    public static void main(String[] args) {
        Stream<String> stream1 = Stream.of("a","b","c");
        Stream<String> stream2 = Stream.of("x", "y", "z");
        // 通过concat方法将两个流合并为一个新的流
        Stream.concat(stream1,stream2).forEach(System.out::println);
    }

4.16 综合案例

定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:

  1. 第一个队伍只保留姓名长度为3的成员
  2. 第一个队伍筛选之后只要前3个人
  3. 第二个队伍只要姓张的成员
  4. 第二个队伍筛选之后不要前两个人
  5. 将两个队伍合并为一个队伍
  6. 根据姓名创建Person对象
  7. 打印整个队伍的Person信息
public class StreamTest21Demo {

    /**
     * 1. 第一个队伍只保留姓名长度为3的成员
     * 2. 第一个队伍筛选之后只要前3个人
     * 3. 第二个队伍只要姓张的成员
     * 4. 第二个队伍筛选之后不要前两个人
     * 5. 将两个队伍合并为一个队伍
     * 6. 根据姓名创建Person对象
     * 7. 打印整个队伍的Person信息
     * @param args
     */
    public static void main(String[] args) {
        List<String> list1 = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七 公");
        List<String> list2 = Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
        // 1. 第一个队伍只保留姓名长度为3的成员
        // 2. 第一个队伍筛选之后只要前3个人
        Stream<String> stream1 = list1.stream().filter(s ->  s.length() == 3).limit(3);
        // 3. 第二个队伍只要姓张的成员
        // 4. 第二个队伍筛选之后不要前两个人
        Stream<String> stream2 = list2.stream().filter(s -> s.startsWith("张")).skip(2);
        // 5. 将两个队伍合并为一个队伍
        // 6. 根据姓名创建Person对象
        // 7. 打印整个队伍的Person信息
        Stream.concat(stream1,stream2)
                //.map(n-> new Person(n))
                .map(Person::new)
                .forEach(System.out::println);

    }
}

输出结果:

Person{name='宋远桥', age=null, height=null}
Person{name='苏星河', age=null, height=null}
Person{name='张二狗', age=null, height=null}
Person{name='张天爱', age=null, height=null}
Person{name='张三', age=null, height=null}

5.Stream结果收集(collect)

collect,收集,可以说是内容最繁多、功能最丰富的部分了。从字面上去理解,就是把一个流收集起来,最终可以是收集成一个值也可以收集成一个新的集合。

collect主要依赖java.util.stream.Collectors类内置的静态方法。

5.1 归集(toList/toSet/toMap)

因为流不存储数据,那么在流中的数据完成处理后,需要将流中的数据重新归集到新的集合里。toListtoSettoMap比较常用,另外还有toCollectiontoConcurrentMap等复杂一些的用法。

下面用一个案例演示toListtoSettoMap

    /**
     * Stream结果收集
     *    收集到集合中
     */
    @Test
    public void test01(){
        // Stream<String> stream = Stream.of("aa", "bb", "cc");
        List<String> list = Stream.of("aa", "bb", "cc","aa")
                .collect(Collectors.toList());
        System.out.println(list);
        // 收集到 Set集合中
        Set<String> set = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toSet());
        System.out.println(set);
        // 收集到 Map集合中
         Map<?, Person> map = personList.stream().filter(p -> p.getSalary() > 8000)
    .collect(Collectors.toMap(Person::getName, p -> p));
        System.out.println("toMap:" + map);
        
        // 如果需要获取的类型为具体的实现,比如:ArrayList HashSet
        ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
                //.collect(Collectors.toCollection(() -> new ArrayList<>()));
                .collect(Collectors.toCollection(ArrayList::new));
        System.out.println(arrayList);
        HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
                .collect(Collectors.toCollection(HashSet::new));
        System.out.println(hashSet);
    }

输出:

[aa, bb, cc, aa]
[aa, bb, cc]
[aa, bb, cc, aa]
[aa, bb, cc]
toMap:{Tom=mutest.Person@5fd0d5ae, Anni=mutest.Person@2d98a335}

5.2 结果收集到数组中

Stream中提供了toArray方法来将结果放到一个数组中,返回值类型是Object[],如果我们要指定返回的类型,那么可以使用另一个重载的toArray(IntFunction f)方法

    /**
     * Stream结果收集到数组中
     */
    @Test
    public void test02(){
        Object[] objects = Stream.of("aa", "bb", "cc", "aa")
                .toArray(); // 返回的数组中的元素是 Object类型
        System.out.println(Arrays.toString(objects));
        // 如果我们需要指定返回的数组中的元素类型
        String[] strings = Stream.of("aa", "bb", "cc", "aa")
                .toArray(String[]::new);
        System.out.println(Arrays.toString(strings));

    }

5.3 统计(count/averaging)

Collectors提供了一系列用于数据统计的静态方法:

  • 计数:count
  • 平均值:averagingIntaveragingLongaveragingDouble
  • 最值:maxByminBy
  • 求和:summingIntsummingLongsummingDouble
  • 统计以上所有:summarizingIntsummarizingLongsummarizingDouble
    /**
     * Stream流中数据的聚合计算
     */
    @Test
    public void test03(){
        // 获取年龄的最大值
        Optional<Person> maxAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.maxBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最大年龄:" + maxAge.get());
        // 获取年龄的最小值
        Optional<Person> minAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.minBy((p1, p2) -> p1.getAge() - p2.getAge()));
        System.out.println("最新年龄:" + minAge.get());
        // 求所有人的年龄之和
        Integer sumAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        )
                //.collect(Collectors.summingInt(s -> s.getAge()))
                .collect(Collectors.summingInt(Person::getAge))
                ;
        System.out.println("年龄总和:" + sumAge);
        // 年龄的平均值
        Double avgAge = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).collect(Collectors.averagingInt(Person::getAge));
        System.out.println("年龄的平均值:" + avgAge);
        // 统计数量
        Long count = Stream.of(
                new Person("张三", 18)
                , new Person("李四", 22)
                , new Person("张三", 13)
                , new Person("王五", 15)
                , new Person("张三", 19)
        ).filter(p->p.getAge() > 18)
                .collect(Collectors.counting());
        System.out.println("满足条件的记录数:" + count);

    }

List<Long> testList = new ArrayList<>(Collections.nCopies(5, 0L));
        testList.set(0,1L);
        testList.set(1,2L);
        testList.set(2,3L);
        testList.set(3,4L);
        testList.set(4,5L);
        System.out.println("sum1 is " + testList.stream().reduce(0L, (a, b) -> a + b));
        // reduce根据初始值(参数1)和累积函数(参数2)依次对数据流进行操作,第一个值与初始值送入累积函数,后面计算结果和下一个数据流依次送入累积函数。
        System.out.println("sum2 is " + testList.stream().reduce(0L, Long::sum));
        System.out.println("sum3 is " + testList.stream().collect(Collectors.summingLong(Long::longValue)));
        // Collectors.summingLong()将流中所有元素视为Long类型,并计算所有元素的总和
        System.out.println("sum4 is " + testList.stream().mapToLong(Long::longValue).sum());
        System.out.println("***********************");
        List<Person> testList1 = new ArrayList<>(Collections.nCopies(5, new Person(1)));
        System.out.println("class sum1 is " + testList1.stream().map(e -> e.getAge()).reduce(0, (a,b) -> a + b));
        System.out.println("class sum2 is " + testList1.stream().map(e -> e.getAge()).reduce(0, Integer::sum));
        System.out.println("class sum3 is " + testList1.stream().collect(Collectors.summingInt(Person::getAge)));
        System.out.println("class sum4 is " + testList1.stream().map(e -> e.getAge()).mapToInt(Integer::intValue).sum());
        return;


案例:统计员工人数、平均工资、工资总额、最高工资。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

  // 求总数
  Long count = personList.stream().collect(Collectors.counting());
  // 求平均工资
  Double average = personList.stream().collect(Collectors.averagingDouble(Person::getSalary));
  // 求最高工资
  Optional<Integer> max = personList.stream().map(Person::getSalary).collect(Collectors.maxBy(Integer::compare));
  // 求工资之和
  Integer sum = personList.stream().collect(Collectors.summingInt(Person::getSalary));
  // 一次性统计所有信息
  DoubleSummaryStatistics collect = personList.stream().collect(Collectors.summarizingDouble(Person::getSalary));

  System.out.println("员工总数:" + count);
  System.out.println("员工平均工资:" + average);
  System.out.println("员工工资总和:" + sum);
  System.out.println("员工工资所有统计:" + collect);
 }
}

运行结果:

员工总数:3
员工平均工资:7900.0
员工工资总和:23700
员工工资所有统计:DoubleSummaryStatistics{count=3, sum=23700.000000,min=7000.000000, average=7900.000000, max=8900.000000}

5.4 分组(groupingBy)

分组:将集合分为多个Map,比如员工按性别分组。有单级分组和多级分组。

​ 当我们使用Stream流处理数据后,可以根据某个属性将数据分组

    /**
     * 分组计算
     */
    @Test
    public void test04(){
        // 根据账号对数据进行分组
        Map<String, List<Person>> map1 = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).collect(Collectors.groupingBy(Person::getName));
        map1.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));
        System.out.println("-----------");
        // 根据年龄分组 如果大于等于18 成年否则未成年
        Map<String, List<Person>> map2 = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).collect(Collectors.groupingBy(p -> p.getAge() >= 18 ? "成年" : "未成年"));
        map2.forEach((k,v)-> System.out.println("k=" + k +"\t"+ "v=" + v));

    }

输出结果:

k=李四	v=[Person{name='李四', age=22, height=177}, Person{name='李四', age=15, height=166}]
k=张三	v=[Person{name='张三', age=18, height=175}, Person{name='张三', age=14, height=165}, Person{name='张三', age=19, height=182}]
-----------
k=未成年	v=[Person{name='张三', age=14, height=165}, Person{name='李四', age=15, height=166}]
k=成年	v=[Person{name='张三', age=18, height=175}, Person{name='李四', age=22, height=177}, Person{name='张三', age=19, height=182}]

多级分组: 先根据name分组然后根据年龄分组

    /**
     * 分组计算--多级分组
     */
    @Test
    public void test05(){
        // 先根据name分组,然后根据age(成年和未成年)分组
        Map<String,Map<Object,List<Person>>> map =  Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).collect(Collectors.groupingBy(
                Person::getName
                ,Collectors.groupingBy(p->p.getAge()>=18?"成年":"未成年")
            )
        );
        map.forEach((k,v)->{
            System.out.println(k);
            v.forEach((k1,v1)->{
                System.out.println("\t"+k1 + "=" + v1);
            });
        });
    }

输出结果:

李四
	未成年=[Person{name='李四', age=15, height=166}]
	成年=[Person{name='李四', age=22, height=177}]
张三
	未成年=[Person{name='张三', age=14, height=165}]
	成年=[Person{name='张三', age=18, height=175}, Person{name='张三', age=19, height=182}]

https://www.jianshu.com/p/fa0249b6ae99

分组排序

5.5 分区(partitioningBy)

分区:将stream按条件分为两个Map,比如员工按薪资是否高于8000分为两部分。

Collectors.partitioningBy会根据值是否为true,把集合中的数据分割为两个列表,一个true列表,一个false列表

在这里插入图片描述

    /**
     * 分区操作
     */
    @Test
    public void test06(){
        Map<Boolean, List<Person>> map = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).collect(Collectors.partitioningBy(p -> p.getAge() > 18));
        map.forEach((k,v)-> System.out.println(k+"\t" + v));
    }

输出结果:

false	[Person{name='张三', age=18, height=175}, Person{name='张三', age=14, height=165}, Person{name='李四', age=15, height=166}]
true	[Person{name='李四', age=22, height=177}, Person{name='张三', age=19, height=182}]

5.6 接合(joining)

Collectors.joining会根据指定的连接符,将所有的元素连接成一个字符串

joining可以将stream中的元素用特定的连接符(没有的话,则直接连接)连接成一个字符串。

    /**
     * 对流中的数据做拼接操作
     */
    @Test
    public void test07(){
        String s1 = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).map(Person::getName)
                .collect(Collectors.joining());
        // 张三李四张三李四张三
        System.out.println(s1);
        String s2 = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).map(Person::getName)
                .collect(Collectors.joining("_"));
        // 张三_李四_张三_李四_张三
        System.out.println(s2);

        String s3 = Stream.of(
                new Person("张三", 18, 175)
                , new Person("李四", 22, 177)
                , new Person("张三", 14, 165)
                , new Person("李四", 15, 166)
                , new Person("张三", 19, 182)
        ).map(Person::getName)
                .collect(Collectors.joining("_", "###", "$$$"));
        // ###张三_李四_张三_李四_张三$$$
        System.out.println(s3);
    }

5.7 归约(reducing)

Collectors类提供的reducing方法,相比于stream本身的reduce方法,增加了对自定义归约的支持。

public class StreamTest {
 public static void main(String[] args) {
  List<Person> personList = new ArrayList<Person>();
  personList.add(new Person("Tom", 8900, 23, "male", "New York"));
  personList.add(new Person("Jack", 7000, 25, "male", "Washington"));
  personList.add(new Person("Lily", 7800, 21, "female", "Washington"));

  // 每个员工减去起征点后的薪资之和(这个例子并不严谨,但一时没想到好的例子)
  Integer sum = personList.stream().collect(Collectors.reducing(0, Person::getSalary, (i, j) -> (i + j - 5000)));
  System.out.println("员工扣税薪资总和:" + sum);

  // stream的reduce
  Optional<Integer> sum2 = personList.stream().map(Person::getSalary).reduce(Integer::sum);
  System.out.println("员工薪资总和:" + sum2.get());
 }
}

运行结果:

员工扣税薪资总和:8700
员工薪资总和:23700

6. 并行的Stream流

streamparallelStream的简单区分: stream是顺序流,由主线程按顺序对流执行操作,而parallelStream是并行流,内部以多线程并行执行的方式对流进行操作,但前提是流中的数据处理没有顺序要求。例如筛选集合中的奇数,两者的处理不同之处:

在这里插入图片描述

如果流中的数据量足够大,并行流可以加快处速度。

除了直接创建并行流,还可以通过parallel()把顺序流转换成并行流:

Optional<Integer> findFirst = list.stream().parallel().filter(x->x>6).findFirst();

6.1 串行的Stream流

我们前面使用的Stream流都是串行,也就是在一个线程上面执行。

    /**
     * 串行流
     */
    @Test
    public void test01(){
        Stream.of(5,6,8,3,1,6)
                .filter(s->{
                    System.out.println(Thread.currentThread() + "" + s);
                    return s > 3;
                }).count();
    }

输出:

Thread[main,5,main]5
Thread[main,5,main]6
Thread[main,5,main]8
Thread[main,5,main]3
Thread[main,5,main]1
Thread[main,5,main]6

6.2 并行流

parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速度。

6.2.1 获取并行流

我们可以通过两种方式来获取并行流。

  1. 通过List接口中的parallelStream方法来获取
  2. 通过已有的串行流转换为并行流(parallel)

实现:

    /**
     * 获取并行流的两种方式
     */
    @Test
    public void test02(){
        List<Integer> list = new ArrayList<>();
        // 通过List 接口 直接获取并行流
        Stream<Integer> integerStream = list.parallelStream();
        // 将已有的串行流转换为并行流
        Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
    }
6.2.2 并行流操作
    /**
     * 并行流操作
     */
    @Test
    public void test03(){

        Stream.of(1,4,2,6,1,5,9)
                .parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理
                .filter(s->{
                    System.out.println(Thread.currentThread() + " s=" +s);
                    return s > 2;
                }).count();
    }

效果

Thread[main,5,main] s=1
Thread[ForkJoinPool.commonPool-worker-2,5,main] s=9
Thread[ForkJoinPool.commonPool-worker-6,5,main] s=6
Thread[ForkJoinPool.commonPool-worker-13,5,main] s=2
Thread[ForkJoinPool.commonPool-worker-9,5,main] s=4
Thread[ForkJoinPool.commonPool-worker-4,5,main] s=5
Thread[ForkJoinPool.commonPool-worker-11,5,main] s=1

6.3 并行流和串行流对比

我们通过for循环,串行Stream流,并行Stream流来对500000000个数字求和。来看消耗时间

public class Test03 {

    private static long times = 500000000;

    private  long start;

    @Before
    public void befor(){
        start = System.currentTimeMillis();
    }

    @After
    public void end(){
        long end = System.currentTimeMillis();
        System.out.println("消耗时间:" + (end - start));
    }

    /**
     * 普通for循环 消耗时间:138
     */
    @Test
    public void test01(){
        System.out.println("普通for循环:");
        long res = 0;
        for (int i = 0; i < times; i++) {
            res += i;
        }
    }

    /**
     * 串行流处理
     *   消耗时间:203
     */
    @Test
    public void test02(){
        System.out.println("串行流:serialStream");
        LongStream.rangeClosed(0,times)
                .reduce(0,Long::sum);
    }

    /**
     * 并行流处理 消耗时间:84
     */
    @Test
    public void test03(){
        LongStream.rangeClosed(0,times)
                .parallel()
                .reduce(0,Long::sum);
    }
}

通过案例我们可以看到parallelStream的效率是最高的。

Stream并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是一个线程操作。

6.4 线程安全问题

在多线程的处理下,肯定会出现数据安全问题。如下:

    @Test
    public void test01(){
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < 1000; i++) {
            list.add(i);
        }
        System.out.println(list.size());//1000
        List<Integer> listNew = new ArrayList<>();
        // 使用并行流来向集合中添加数据
        list.parallelStream()
                //.forEach(s->listNew.add(s));
                .forEach(listNew::add);
        System.out.println(listNew.size());//839或异常
    }

运行效果:

839

或者直接抛异常

java.lang.ArrayIndexOutOfBoundsException
	at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
	at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
	at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
	at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
	at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
....
Caused by: java.lang.ArrayIndexOutOfBoundsException: 366
	at java.util.ArrayList.add(ArrayList.java:463)

针对这个问题,我们的解决方案有哪些呢?

  1. 加同步锁
  2. 使用线程安全的容器
  3. 通过Stream中的toArray/collect操作

实现:

    /**
     * 加同步锁
     */
    @Test
    public void test02(){
        List<Integer> listNew = new ArrayList<>();
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                    synchronized (obj){
                        listNew.add(i);
                    }

                });
        System.out.println(listNew.size());
    }

    /**
     * 使用线程安全的容器
     */
    @Test
    public void test03(){
        Vector v = new Vector();
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                    synchronized (obj){
                        v.add(i);
                    }

                });
        System.out.println(v.size());
    }

    /**
     * 将线程不安全的容器转换为线程安全的容器
     */
    @Test
    public void test04(){
        List<Integer> listNew = new ArrayList<>();
        // 将线程不安全的容器包装为线程安全的容器
        List<Integer> synchronizedList = Collections.synchronizedList(listNew);
        Object obj = new Object();
        IntStream.rangeClosed(1,1000)
                .parallel()
                .forEach(i->{
                        synchronizedList.add(i);
                });
        System.out.println(synchronizedList.size());
    }


    /**
     * 我们还可以通过Stream中的 toArray方法或者 collect方法来操作
     * 就是满足线程安全的要求
     * IntStream boxed()返回一个包含此流元素的流,每个元素都装在整数中。
     */
    @Test
    public void test05(){
        List<Integer> listNew = new ArrayList<>();
        Object obj = new Object();
        List<Integer> list = IntStream.rangeClosed(1, 1000)
                .parallel()
                .boxed()
                .collect(Collectors.toList());
        System.out.println(list.size());
    }
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JDK8之Stream流 的相关文章

随机推荐

  • 内网穿透 VScodeSHH

    准备 腾讯云服务器 linux xshell xftp frp https github com fatedier frp 服务端为腾讯云服务器 linux 客户端为自己工作站 linux 服务端操作 用xshell登录腾讯云服务器 下载
  • audio标签与video标签的常用属性及方法

    一 常用的css属性 1 src 用于指明video标签需要播放的音频的地址
  • C语言学习之认识exit()函数

    C语言学习之认识exit 函数 在C语言的main函数中我们通常使用return 0 exit 0 表示程序正常退出 exit exit 1 表示程序异常退出 exit 结束当前进程 当前程序 在整个程序中 只要调用 exit 就结束 但在
  • unity下HybridCLR热更新简易框架

    简易打AB包工具 using System Collections using System Collections Generic using UnityEngine using UnityEditor using HybridCLR E
  • servlet的生命周期

    Servlet的生命周期可以分为5个阶段 加载阶段 创建阶段 初始化阶段 处理客户请求 服务 阶段 销毁阶段 1 加载阶段 服务器接收到客户端请求之后首先会通过类加载器使用servlet类对应的文件加载servlet 2 创建阶段 然后we
  • 河海大学计算机esi排名,河海大学材料科学学科进入ESI国际排名前1%

    根据汤森路透基本科学指标数据库 Essential Science Indicators 简称ESI 2018年3月最新公布的数据 河海大学材料科学学科继工程学 环境 生态学 计算机科学之后首次进入ESI国际学科排名全球前1 实现了学校学科
  • 面试算法题:O(nlogn)查询l~r区间内k的个数

    查询用户文章喜好 我们对用户按照它们的注册时间先后来标号 对于一类文章 每个用户都有不同的喜好值 我们会想知道某一段时间内注册的用户 标号相连的一批用户 中 有多少用户对这类文章喜好值为k 因为一些特殊的原因 不会出现一个查询的用户区间完全
  • 小程序/js/uni订单金额播放

    之前发现直接播放会自动中断 所以纠结了半天 先说思路 首先金额转成字符串 然后截取 得到两段数组 然后遍历 前段需要追加单位 后段小数点后 之后得到需要播放的音频数组 在每段播放完进行完后播放下一段 语音包有点问题就不放了 还是看代码吧 d
  • Chrome浏览器命令行启动参数

    Chrome浏览器命令行启动参数 http blog csdn net qq 32786873 article details 70173265 http blog csdn net u012593626 article details 4
  • Markdown 基本语法

    Markdown 基本语法 初级 一 什么是Markdown Markdown 是一种轻量级标记语言 它允许人们使用易读易写的纯文本格式编写文档 将格式元素添加到纯文本文档 Markdown 允许您使用易于阅读 易于编写的纯文本格式进行编写
  • [运营专题]零预算引爆个人和企业品牌

    文章推荐 Selenium 自动化测试从零实战 原文链接 原来这样做 才能向架构师靠近 原文链接 Cordova App 打包全揭秘 原文链接 TensorFlow on Android 物体识别 原文链接 TensorFlow on An
  • File 类和 InputStream, OutputStream 的用法总结

    目录 一 File 类 1 File类属性 2 构造方法 3 普通方法 二 InputStream 1 方法 2 FileInputStream 三 OutputStream 1 方法 2 FileOutputStream 四 针对字符流对
  • 点云数据生成三维模型_Agisoftphotoscan生成三维模型步骤

    随着航空测量技术的飞速发展 利用低空无人飞机进行航空摄影获取遥感数据已成为现实 利用Agisoftphotoscan软件进行影像数据处理 生成数字地表模型 DSM 和正射影像图 DOM 产品的生产 数据生产流程 1 无人机的用途及种类的不同
  • FFmpeg编译配置命令

    configure help Usage configure options Options defaults in brackets after descriptions Help options help print this mess
  • 华为OD机试 - 城市聚集度(Java)

    题目描述 一张地图上有n个城市 城市和城市之间有且只有一条道路相连 要么直接相连 要么通过其它城市中转相连 可中转一次或多次 城市与城市之间的道路都不会成环 当切断通往某个城市 i 的所有道路后 地图上将分为多个连通的城市群 设该城市i的聚
  • 我在项目中遇到的一些经典功能bug

    1 传参类型不同 类型是数组 实际传的是字符串 导致重置搜索条件后导出失败 刷新页面 或者切换中英文也是刷新页面的效果 初始化赋值为null 可以导出成功 重置搜索条件后 导出失败 2 下划线 百分号 可能是适配符 特殊字符 空格 边界值
  • AlSD 系列智能安全配电装置是安科瑞电气有限公司专门为低压配电侧开发的一款智能安全用电产 品-安科瑞黄安南

    一 应用背景 电力作为一种清洁能源 给人们带来了舒适 便捷的电气化生活 与此同时 由于使用不当 维护 不及时等原因引发的漏电触电和电气火灾事故 也给人们的生命和财产带来了巨大的威胁和损失 为了防止低压配电系统发生漏电和电气火灾事故 传统的方
  • 网络运维词汇汇总

    本篇之所以起该名字 是因为我在一家网络公司工作所遇到的一些相关词汇 仅供参考 1 关系型数据库服务 RDS 关系型数据库服务 RelationalDatabase Service 简称RDS 是一种稳定可靠 可弹性伸缩的在线数据库服务 RD
  • Metasploitable2在VMware上的安装与初步渗透学习

    环境 靶机 Metasploitable2 IP 未知 攻击机 KALI IP 192 168 127 5 平台 VMware 16 2 4 一 Metasploitable2的简介 Metasploitable2是一个故意易受攻击的Lin
  • JDK8之Stream流

    1 集合处理数据的弊端 当我们在需要对集合中的元素进行操作的时候 除了必需的添加 删除 获取外 最典型的操作就是集合遍历 public class StreamTest01 public static void main String ar