https://www.cnblogs.com/yangsanluo/p/14976907.html:通过stream——>list转map

https://nimbletext.com/Live——批量sql

1. Lambda表达式

1.1 需求分析

创建一个新的线程,指定线程要执行的任务

  1. public static void main(String[] args) {
  2. // 开启一个新的线程
  3. new Thread(new Runnable() {
  4. @Override
  5. public void run() {
  6. System.out.println("新线程中执行的代码 :
  7. "+Thread.currentThread().getName());
  8. }
  9. }).start();
  10. System.out.println("主线程中的代码:" + Thread.currentThread().getName());

代码分析
1. Thread类需要一个Runnable接口作为参数,其中的抽象方法run方法是用来指定线程任务内容的
核心
2. 为了指定run方法体,不得不需要Runnable的实现类
3. 为了省去定义一个Runnable 的实现类,不得不使用匿名内部类
4. 必须覆盖重写抽象的run方法,所有的方法名称,方法参数,方法返回值不得不都重写一遍,而且
不能出错,
5. 而实际上,我们只在乎方法体中的代码

1.2 Lambda表达式初体验

Lambda表达式是一个匿名函数,可以理解为一段代码的传递

  1. new Thread(() ->
  2. { System.out.println("新线程Lambda表达式..." +Thread.currentThread().getName());
  3. }) .start();

Lambda表达式的优点:Lambda表达式简化了匿名内部类的使用,语法更加的简单

1.3 Lambda表达式的语法规则

Lambda表达式省去了面向对象的条条框框,Lambda的标准格式分为3部分

  1. (参数类型 参数名称)->{
  2. 代码体;
  3. };
  • (参数类型 参数名称):参数列表
  • {代码体}:方法体
  • ->::导向符号,分割参数列表和方法体

    1.3.1 Lambda表达式练习01

    定义接口UserService

    1. public interface UserService {
    2. void show();
    3. }
    1. //Lambda表达式练习01,无参数无返回值
    2. @Test
    3. public void testLambda02() {
    4. goShow(new UserService() {
    5. @Override
    6. public void show() {
    7. System.out.println("show方法执行了");
    8. }
    9. });
    10. goShow(()->{
    11. System.out.println("Lambda Show方法执行了");
    12. });
    13. }
    14. private static void goShow(UserService userService) {
    15. userService.show();
    16. }

    1.3.2 Lambda表达式练习02

    我们发现在sort方法的第二个参数是一个Comparator接口的匿名内部类,且执行的方法有参数和返回
    值,那么我们可以改写为Lambda表达式

    1. //练习02:完成一个有参且有返回值得Lambda表达式案例
    2. //创建一个Person对象
    3. @Test
    4. public void testLambda03(){
    5. List<Person> list = new ArrayList<>();
    6. list.add(new Person("周杰伦", 33, 175));
    7. list.add(new Person("刘德华", 43, 185));
    8. list.add(new Person("周星驰", 38, 177));
    9. list.add(new Person("郭富城", 23, 170));
    10. // Collections.sort(list, new Comparator<Person>() {
    11. // @Override
    12. // public int compare(Person o1, Person o2) {
    13. // return o2.getAge()-o1.getAge();
    14. // }
    15. // });
    16. // for (Person p :list) {
    17. // System.out.println(p);
    18. // }
    19. Collections.sort(list,(Person o1, Person o2)->{
    20. return o1.getAge()-o2.getAge();
    21. });
    22. for (Person p :list) {
    23. System.out.println(p);
    24. }
    25. }

    1.4 @FunctionalInterface注解

    ```java /**

  • @FunctionalInterface
  • 这是一个标志注解,被该注解修饰的接口只能声明一个抽象方法 */ @FunctionalInterface public interface UserService { void show(); }
    1. <a name="OX61m"></a>
    2. ##### 1.5 Lambda表达式的原理
    3. 匿名内部类的本质是在编译时生成一个Class 文件。XXXXX$1.class
    4. ```java
    5. public class Demo01Lambda {
    6. public static void main(String[] args) {
    7. // 开启一个新的线程 new Thread(new Runnable() {
    8. @Override public void run() {
    9. System.out.println("新线程中执行的代码 : "+Thread.currentThread().getName());
    10. }
    11. }).start();
    12. System.out.println("主线程中的代码:" + Thread.currentThread().getName());
    13. System.out.println("---------------");
    14. /*new Thread(() -> { System.out.println("新线程Lambda表达式..." +Thread.currentThread().getName()); }) .start();
    15. */
    16. }
    17. }
    还可以通过反编译工具来查看生成的代码 XJad 工具来查看
    1. static class Demo01Lambda$1 implements Runnable {
    2. public void run() {
    3. System.out.println((new StringBuilder()).append("新线程中执行的代码 : " ).append(Thread.currentThread().getName()).toString());
    4. }
    5. Demo01Lambda$1() {
    6. }
    7. }
    那么Lambda表达式的原理是什么呢?我们也通过反编译工具来查看
    1.6 Lambda表达式的省略写法
    在lambda表达式的标准写法基础上,可以使用省略写法的规则为:
    1. 小括号内的参数类型可以省略
    2. 如果小括号内有且仅有一个参数,则小括号可以省略
    3. 如果大括号内有且仅有一个语句,可以同时省略大括号,return 关键字及语句分号。
    1. public class Demo05Lambda {
    2. public static void main(String[] args) {
    3. goStudent((String name,Integer age)->{
    4. return name+age+" 6666 ...";
    5. });
    6. // 省略写法
    7. goStudent((name,age)-> name+age+" 6666 ...");
    8. System.out.println("------");
    9. goOrder((String name)->{
    10. System.out.println("--->" + name); return 666;
    11. });
    12. // 省略写法
    13. goOrder(name -> {
    14. System.out.println("--->" + name); return 666;
    15. });
    16. goOrder(name -> 666);
    17. }
    18. public static void goStudent(StudentService studentService){
    19. studentService.show("张三",22);
    20. }
    21. public static void goOrder(OrderService orderService){
    22. orderService.show("李四");
    23. }
    24. }
    1.7 Lambda表达式的使用前提
    Lambda表达式的语法事非常简洁的,但是不是随便使用的,使用时有几个条件要特别注意:
    1.方法的参数或局部变量类型必须为接口才能使用Lambda表达式
    2.接口中有且只有一个抽象方法(@FunctionalInterface)
    1.8 Lambda和匿名内部类的对比
    Lambda和匿名内部类的对比
    1.所属类型不一样
  • 匿名内部类的类型可以时类,抽象类,接口
  • Lambda表达式需要的类型必须是接口

2.抽象方法的数量不一样

  • 匿名内部类所需的接口中的抽象方法的数量是随意的
  • Lambda表达式所需的接口中只能有一个抽象方法

3.实现原理不一样

  • 匿名内部类是再编译后形成的一个class
  • Lambda表达式是在程序运行的时候动态生成class

    2. 函数式接口

    3. 方法引用与构造器引用

    4. Stream Api

    4.1 集合处理数据的弊端

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

    1. public class StreamTest01 {
    2. public static void main(String[] args) {
    3. // 定义一个List集合
    4. List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
    5. // 1.获取所有 姓张的信息
    6. List<String> list1 = new ArrayList<>();
    7. for (String s : list) {
    8. if(s.startsWith("张")){
    9. list1.add(s);
    10. }
    11. }
    12. // 2.获取名称长度为3的用户
    13. List<String> list2 = new ArrayList<>();
    14. for (String s : list1) {
    15. if(s.length() == 3){
    16. list2.add(s);
    17. }
    18. }
    19. // 3. 输出所有的用户信息
    20. for (String s : list2) {
    21. System.out.println(s);
    22. }
    23. }
    24. }

    上面的代码针对我们不同的需求一次次的循环,这时我们希望有更加高效的处理方式,这时我们就可以通过JDK1.8中提供的Stream API来解决这个问题
    Stream流过滤filter,配合forEach使用

    1. //通过Stream流过滤filter,配合forEach使用-->list
    2. @Test
    3. public void testStreamFilter(){
    4. // 定义一个List集合
    5. List<String> list = Arrays.asList("张三","张三丰","成龙","周星驰");
    6. // 1.获取所有 姓张的信息
    7. // 2.获取名称长度为3的用户
    8. // 3. 输出所有的用户信息
    9. list.stream().filter(s->s.startsWith("张"))
    10. .filter(s->s.length() == 3)
    11. .forEach(s->{
    12. System.out.println(s);
    13. });
    14. }

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

    4.2 Stream流式思想概述

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

    4.3 Stream流的获取方式

    4.3.1 根据Collection获取

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

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

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

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

    4.3.2 通过Stream的of方法

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

    1. //Stream 中的静态方法Of
    2. @Test
    3. public void testStreamOf(){
    4. Stream<String> a1 = Stream.of("a1", "a2", "a3");
    5. String[] arr1 = {"aa","bb","cc"};
    6. Stream<String> arr11 = Stream.of(arr1);
    7. Integer[] arr2 = {1,2,3,4};
    8. Stream<Integer> arr21 = Stream.of(arr2);
    9. arr21.forEach(ss->{
    10. System.out.println(ss);
    11. });
    12. // 注意:基本数据类型的数组是不行的
    13. int[] arr3 = {1,2,3,4};
    14. Stream.of(arr3).forEach(s->{
    15. System.out.println(s);
    16. });
    17. }

    4.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.4.1 forEach方法
    forEach用来遍历流中的数据的
    1. //forEach方法
    2. void forEach(Consumer<? super T> action);
    该方法接受一个Consumer接口,会将每一个流元素交给函数处理
    1. //forEach方法
    2. @Test
    3. public void testForEach() {
    4. List list = new ArrayList<>();
    5. Stream.of("1","2","3").forEach(s->{
    6. list.add(s);
    7. });
    8. list.stream().forEach(System.out::println);
    9. }
    4.4.2 count方法
    Stream流中的count方法用来统计其中的元素个数的
    1. long count();
    该方法返回一个long值,代表元素的个数
    1. //count计数
    2. @Test
    3. public void testCount() {
    4. long count = Stream.of("2", "3", "4").count();
    5. List<Object> list = new ArrayList<>();
    6. list.add("6");
    7. list.add("8");
    8. list.add("9");
    9. list.add("10");
    10. long count1 = list.stream().count();
    11. System.out.println(count1);
    12. }
    4.4.3 filter方法
    filter方法的作用是用来过滤数据的。返回符合条件的数据
    filter.PNG
    可以通过filter方法将一个流转换成另一个子集流
    1. Stream<T> filter(Predicate<? super T> predicate);
    该接口接收一个Predicate函数式接口参数作为筛选条件
    1. //filter
    2. @Test
    3. public void testFilter() {
    4. List<String> list = new ArrayList<>();
    5. list.add("a6");
    6. list.add("a8");
    7. list.add("a9");
    8. list.add("b10");
    9. list.stream().filter(s->s.contains("a"))//包含a的全部筛选出来
    10. .forEach(s->{
    11. System.out.println(s);
    12. });
    13. }
    4.4.4 limit方法 取前几个
    捕获limit.PNG
    limit方法可以对流进行截取处理,只取前几个数据,得到一个处理后的流
    1. Stream<T> limit(long maxSize);
    参数是一个long类型的数值,如果集合当前长度大于参数就进行截取,否则不操作:
    1. //limit 方法截取前几个数据
    2. @Test
    3. public void testLimit() {
    4. List<String> list = new ArrayList<>();
    5. list.add("a6");
    6. list.add("a8");
    7. list.add("a9");
    8. list.add("b10");
    9. list.stream().limit(2).forEach(s->{
    10. System.out.println(s);
    11. });
    12. }
    13. //结果:a6,a8
    4.4.5 skip方法 跳过前几个
    Stream的skip方法.PNG
    如果希望跳过前面几个元素,可以使用skip方法获取一个截取之后的流
    1. Stream<T> skip(long n);
    实现:
    1. //skip 方法截取前几个数据
    2. @Test
    3. public void testSkip() {
    4. List<String> list = new ArrayList<>();
    5. list.add("a6");
    6. list.add("a8");
    7. list.add("a9");
    8. list.add("b10");
    9. list.stream().skip(2).forEach(s->{
    10. System.out.println(s);
    11. });
    12. }
    13. //结果:a9,a10
    4.4.6 map方法
    如果我们需要将流中的元素映射到另一个流中,可以使用map这个方法:
    1. <R> Stream<R> map(Function<? super T, ? extends R> mapper);
    map.PNG
    该接口需要一个Function函数式接口参数,可以将当前流中的T类型数据转换为另一种R类型的数据
    1. //map 方法映射
    2. @Test
    3. public void testMap() {
    4. List<String> list = new ArrayList<>();
    5. list.add("6");
    6. list.add("8");
    7. list.add("9");
    8. list.add("10");
    9. list.stream().map(s->Integer.parseInt(s))
    10. .forEach(System.out::println);
    11. }
    12. //结果:String集合映射成Integer集合
    4.4.7 sorted排序方法
    如果需要将数据排序,可以使用sorted方法:
    1. Stream<T> sorted();
    在使用的时候可以根据自然规则排序,也可以通过比较强来指定对应的排序规则
    1. //sorted 方法映射
    2. @Test
    3. public void testSorted() {
    4. List<String> list = new ArrayList<>();
    5. list.add("88");
    6. list.add("8");
    7. list.add("9");
    8. list.add("10");
    9. list.stream().map(s->Integer.parseInt(s))
    10. .sorted((s1,s2)->{return s1-s2;})//指定的方式进行排序,从小到大
    11. .forEach(System.out::println);
    12. }
    4.4.8 distinct去重方法
    如果要去掉重复数据,可以使用distinct方法:
    1. Stream<T> distinct();
    distinct去重方法.PNG
    实现:
    1. //distinct 去重的方法
    2. @Test
    3. public void testDistinct() {
    4. List<String> list = new ArrayList<>();
    5. list.add("8");
    6. list.add("8");
    7. list.add("9");
    8. list.add("10");
    9. list.stream().map(s->Integer.parseInt(s))
    10. .sorted((s1,s2)->{return s1-s2;})
    11. .distinct()
    12. .forEach(System.out::println);
    13. System.out.println("=======================");
    14. //自定义的实体类过滤
    15. List<Person> personList = new ArrayList<>();
    16. personList.add(new Person("周杰伦", 33, 175));
    17. personList.add(new Person("刘德华", 43, 185));
    18. personList.add(new Person("周星驰", 38, 177));
    19. personList.add(new Person("周杰伦", 33, 175));
    20. personList.stream().distinct()
    21. .forEach(System.out::println);
    22. }
    注意:Stream使用distinct去重基本数据类型可以直接去重,但是自定类型需要重写hashCode和equals方法来移除重复的元素。
    4.4.9 match 方法 条件判断
    1. boolean anyMatch(Predicate<? super T> predicate); // 元素是否有任意一个满足条件
    2. boolean allMatch(Predicate<? super T> predicate); // 元素是否都满足条件
    3. boolean noneMatch(Predicate<? super T> predicate); // 元素是否都不满足条件
    实现:
    1. public static void main(String[] args) {
    2. boolean b = Stream.of("1", "3", "3", "4", "5", "1", "7")
    3. .map(Integer::parseInt)
    4. //.allMatch(s -> s > 0)
    5. //.anyMatch(s -> s >4)
    6. .noneMatch(s -> s > 4) ;
    7. System.out.println(b);
    8. }
    注意:match是个终结方法
    4.4.10 find方法 找其中某一个
    如果我们需要找到某些数据,可以使用find方法来实现
    1. Optional<T> findFirst(); //找到第一个返回
    2. Optional<T> findAny(); //找到一个就返回
    find方法.PNG
    实现:
    1. public static void main(String[] args) {
    2. Optional<String> first = Stream.of("1", "3", "3", "4", "5", "1", "7")
    3. .findFirst();
    4. System.out.println(first.get());
    5. Optional<String> any = Stream.of("1", "3", "3", "4", "5", "1", "7")
    6. .findAny();
    7. System.out.println(any.get());
    8. }
    4.4.11 max和min方法
    取最大值和最小值
    1. Optional<T> min(Comparator<? super T> comparator);
    2. Optional<T> max(Comparator<? super T> comparator);
    实现:
    1. public static void main(String[] args) {
    2. Optional<Integer> max = Stream.of("1", "3", "3", "4", "5", "1", "7")
    3. .map(Integer::parseInt)
    4. .max((o1,o2)->o1-o2);
    5. System.out.println(max.get());
    6. Optional<Integer> min = Stream.of("1", "3", "3", "4", "5", "1", "7")
    7. .map(Integer::parseInt)
    8. .min((o1,o2)->o1-o2);
    9. System.out.println(min.get()); }
    4.4.412 reduce方法
    如果需要将所有数据归纳得到一个数据,可以使用reduce方法
    1. T reduce(T identity, BinaryOperator<T> accumulator);
    实现:
    1. public static void main(String[] args) {
    2. Integer sum = Stream.of(4, 5, 3, 9)
    3. // identity默认值
    4. // 第一次的时候会将默认值赋值给x
    5. // 之后每次会将 上一次的操作结果赋值给x y就是每次从数据中获取的元素
    6. .reduce(0, (x, y) -> {
    7. System.out.println("x="+x+",y="+y);
    8. return x + y;
    9. });
    10. System.out.println(sum);
    11. // 获取 最大值
    12. Integer max = Stream.of(4, 5, 3, 9)
    13. .reduce(0, (x, y) -> {
    14. return x > y ? x : y;
    15. });
    16. System.out.println(max);
    17. }
    4.4.13 concat两个流合并
    如果有两个流,希望合并成为一个流,那么可以使用Stream接口的静态方法concat
    1. public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) {
    2. Objects.requireNonNull(a);
    3. Objects.requireNonNull(b);
    4. @SuppressWarnings("unchecked")
    5. Spliterator<T> split = new Streams.ConcatSpliterator.OfRef<>(
    6. (Spliterator<T>) a.spliterator(), (Spliterator<T>)
    7. b.spliterator());
    8. Stream<T> stream = StreamSupport.stream(split, a.isParallel() || b.isParallel());
    9. return stream.onClose(Streams.composedClose(a, b));
    10. }
    实现:
    1. public static void main(String[] args) {
    2. Stream<String> stream1 = Stream.of("a","b","c");
    3. Stream<String> stream2 = Stream.of("x", "y", "z");
    4. // 通过concat方法将两个流合并为一个新的流
    5. Stream.concat(stream1,stream2).forEach(System.out::println);
    6. }
    4.4.14 综合案例
    定义两个集合,然后在集合中存储多个用户名称。然后完成如下的操作:
    1. 第一个队伍只保留姓名长度为3的成员
    2. 第一个队伍筛选之后只要前3个人
    3. 第二个队伍只要姓张的成员
    4. 第二个队伍筛选之后不要前两个人
    5. 将两个队伍合并为一个队伍
    6. 根据姓名创建Person对象
    7. 打印整个队伍的Person信息
    1. /**
    2. * 综合案例:
    3. * 1. 第一个队伍只保留姓名长度为3的成员
    4. * 2. 第一个队伍筛选之后只要前3个人
    5. * 3. 第二个队伍只要姓张的成员
    6. * 4. 第二个队伍筛选之后不要前两个人
    7. * 5. 将两个队伍合并为一个队伍
    8. * 6. 根据姓名创建Person对象
    9. * 7. 打印整个队伍的Person信息
    10. */
    11. @Test
    12. public void testPerson() {
    13. List<String> list1 = Arrays.asList("迪丽热巴", "宋远桥", "苏星河", "老子", "庄子", "孙子", "洪七 公");
    14. List<String> list2 = Arrays.asList("古力娜扎", "张无忌", "张三丰", "赵丽颖", "张二狗", "张天爱", "张三");
    15. // 1. 第一个队伍只保留姓名长度为3的成员
    16. // 2. 第一个队伍筛选之后只要前3个人
    17. Stream<String> stream1 = list1.stream().filter(s -> s.length() == 3)
    18. .limit(3);
    19. // 3. 第二个队伍只要姓张的成员
    20. // 4. 第二个队伍筛选之后不要前两个人
    21. Stream<String> stream2 = list2.stream().filter(s -> s.startsWith("张"))
    22. .skip(2);
    23. //5. 将两个队伍合并为一个队伍
    24. //6. 根据姓名创建Person对象
    25. //7. 打印整个队伍的Person信息
    26. Stream.concat(stream1,stream2)
    27. .map(s->new Person(s))
    28. .forEach(p->{
    29. System.out.println(p);
    30. });
    31. }

    4.5 Stream 结果收集

    4.5.1 结果收集到集合中
    1. /**
    2. * Stream结果收集
    3. * 收集到集合中
    4. */
    5. @Test
    6. public void test01(){
    7. // Stream<String> stream = Stream.of("aa", "bb", "cc");
    8. List<String> list = Stream.of("aa", "bb", "cc","aa")
    9. .collect(Collectors.toList());
    10. System.out.println(list);
    11. // 收集到 Set集合中
    12. Set<String> set = Stream.of("aa", "bb", "cc", "aa")
    13. .collect(Collectors.toSet());
    14. System.out.println(set);
    15. // 如果需要获取的类型为具体的实现,比如:
    16. ArrayList HashSet ArrayList<String> arrayList = Stream.of("aa", "bb", "cc", "aa")
    17. //.collect(Collectors.toCollection(() -> new ArrayList<>()));
    18. .collect(Collectors.toCollection(ArrayList::new));
    19. System.out.println(arrayList);
    20. HashSet<String> hashSet = Stream.of("aa", "bb", "cc", "aa")
    21. .collect(Collectors.toCollection(HashSet::new));
    22. System.out.println(hashSet);
    23. }
    4.5.2 结果收集到数组中
    Stream中提供了toArray方法来将结果放到一个数组中,返回值类型是Object[],如果我们要指定返回的
    类型,那么可以使用另一个重载的toArray(IntFunction f)方法
    1. /**
    2. * Stream结果收集到数组中
    3. */
    4. @Test
    5. public void test02(){
    6. Object[] objects = Stream.of("aa", "bb", "cc", "aa")
    7. .toArray(); // 返回的数组中的元素是 Object类型
    8. System.out.println(Arrays.toString(objects));
    9. // 如果我们需要指定返回的数组中的元素类型
    10. String[] strings = Stream.of("aa", "bb", "cc", "aa")
    11. .toArray(String[]::new);
    12. System.out.println(Arrays.toString(strings));
    13. }

    4.6 并行的Stream流

    4.6.1 串行的Stream流
    我们前面使用的Stream流都是串行,也就是在一个线程上面执行。
    1. /**
    2. * 串行流
    3. */
    4. @Test
    5. public void test01(){
    6. Stream.of(5,6,8,3,1,6)
    7. .filter(s->{
    8. System.out.println(Thread.currentThread() + "" + s);
    9. return s > 3;
    10. }).count();
    11. }
    1. 响应结果:
    2. Thread[main,5,main]5
    3. Thread[main,5,main]6
    4. Thread[main,5,main]8
    5. Thread[main,5,main]3
    6. Thread[main,5,main]1
    7. Thread[main,5,main]6
    4.6.2 并行流
    parallelStream其实就是一个并行执行的流,它通过默认的ForkJoinPool,可以提高多线程任务的速
    度。
  • 获取并行流

我们可以通过两种方式来获取并行流。
1. 通过List接口中的parallelStream方法来获取
2. 通过已有的串行流转换为并行流(parallel)
实现:

  1. /**
  2. * 获取并行流的两种方式
  3. */
  4. @Test
  5. public void test02(){
  6. List<Integer> list = new ArrayList<>();
  7. // 通过List 接口 直接获取并行流
  8. Stream<Integer> integerStream = list.parallelStream();
  9. // 将已有的串行流转换为并行流
  10. Stream<Integer> parallel = Stream.of(1, 2, 3).parallel();
  11. }
  • 并行流操作
    1. /**
    2. * 并行流操作
    3. */
    4. @Test
    5. public void test03(){
    6. Stream.of(1,4,2,6,1,5,9)
    7. .parallel() // 将流转换为并发流,Stream处理的时候就会通过多线程处理
    8. .filter(s->{
    9. System.out.println(Thread.currentThread() + " s=" +s);
    10. return s > 2;
    11. }).count();
    12. }
    效果:
    1. Thread[main,5,main] s=1
    2. Thread[ForkJoinPool.commonPool-worker-2,5,main] s=9
    3. Thread[ForkJoinPool.commonPool-worker-6,5,main] s=6
    4. Thread[ForkJoinPool.commonPool-worker-13,5,main] s=2
    5. Thread[ForkJoinPool.commonPool-worker-9,5,main] s=4
    6. Thread[ForkJoinPool.commonPool-worker-4,5,main] s=5
    7. Thread[ForkJoinPool.commonPool-worker-11,5,main] s=1
    4.6.3 并行流和串行流对比
    我们通过for循环,串行Stream流,并行Stream流来对500000000亿个数字求和。来看消耗时间
    1. public class Test03 {
    2. private static long times = 500000000;
    3. private long start;
    4. @Before
    5. public void befor(){
    6. start = System.currentTimeMillis();
    7. }
    8. @After
    9. public void end(){
    10. long end = System.currentTimeMillis();
    11. System.out.println("消耗时间:" + (end - start));
    12. }
    13. /**
    14. * 普通for循环 消耗时间:138
    15. */
    16. @Test
    17. public void test01(){
    18. System.out.println("普通for循环:");
    19. long res = 0;
    20. for (int i = 0; i < times; i++) {
    21. res += i;
    22. }
    23. }
    24. /**
    25. * 串行流处理
    26. * 消耗时间:203
    27. */
    28. @Test
    29. public void test02(){
    30. System.out.println("串行流:serialStream");
    31. LongStream.rangeClosed(0,times) .reduce(0,Long::sum);
    32. }
    33. /**
    34. * 并行流处理 消耗时间:84
    35. */
    36. @Test
    37. public void test03(){
    38. LongStream.rangeClosed(0,times) .parallel() .reduce(0,Long::sum);
    39. }
    40. }
    通过案例我们可以看到parallelStream的效率是最高的。
    Stream并行处理的过程会分而治之,也就是将一个大的任务切分成了多个小任务,这表示每个任务都是
    一个线程操作。
    4.6.4 线程安全问题
    在多线程的处理下,肯定会出现线程安全问题。如下:
    1. @Test
    2. public void test01(){
    3. List<Integer> list = new ArrayList<>();
    4. for (int i = 0; i < 1000; i++) {
    5. list.add(i);
    6. }
    7. System.out.println(list.size());
    8. List<Integer> listNew = new ArrayList<>();
    9. // 使用并行流来向集合中添加数据
    10. list.parallelStream()
    11. //.forEach(s->listNew.add(s));
    12. .forEach(listNew::add);
    13. System.out.println(listNew.size());
    14. }
    运行结果:
    1. 839
    或者直接抛异常
    1. java.lang.ArrayIndexOutOfBoundsException
    2. at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
    3. at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorI mpl.java:62)
    4. at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorA ccessorImpl.java:45)
    5. at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
    6. at java.util.concurrent.ForkJoinTask.getThrowableException(ForkJoinTask.java:598)
    7. ....
    8. Caused by: java.lang.ArrayIndexOutOfBoundsException: 366
    9. at java.util.ArrayList.add(ArrayList.java:463)
    针对这个问题,我们的解决方案有哪些呢?
    1. 加同步锁
    2. 使用线程安全的容器
    3. 通过Stream中的toArray/collect操作
    实现:
    1. /**
    2. * 加同步锁
    3. */
    4. @Test
    5. public void test02(){
    6. List<Integer> listNew = new ArrayList<>();
    7. Object obj = new Object();
    8. IntStream.rangeClosed(1,1000)
    9. .parallel()
    10. .forEach(i->{
    11. synchronized (obj){
    12. listNew.add(i);
    13. }
    14. });
    15. System.out.println(listNew.size());
    16. }
    17. /**
    18. * 使用线程安全的容器
    19. */
    20. @Test
    21. public void test03(){
    22. Vector v = new Vector();
    23. Object obj = new Object();
    24. IntStream.rangeClosed(1,1000)
    25. .parallel()
    26. .forEach(i->{
    27. synchronized (obj){
    28. v.add(i);
    29. }
    30. });
    31. System.out.println(v.size());
    32. }
    33. /**
    34. * 将线程不安全的容器转换为线程安全的容器
    35. */
    36. @Test
    37. public void test04(){
    38. List<Integer> listNew = new ArrayList<>();
    39. // 将线程不安全的容器包装为线程安全的容器
    40. List<Integer> synchronizedList = Collections.synchronizedList(listNew);
    41. Object obj = new Object();
    42. IntStream.rangeClosed(1,1000)
    43. .parallel()
    44. .forEach(i->{
    45. synchronizedList.add(i);
    46. });
    47. System.out.println(synchronizedList.size());
    48. }
    49. /**
    50. * 我们还可以通过Stream中的 toArray方法或者 collect方法来操作
    51. * 就是满足线程安全的要求
    52. */
    53. @Test
    54. public void test05(){
    55. List<Integer> listNew = new ArrayList<>();
    56. Object obj = new Object();
    57. List<Integer> list = IntStream.rangeClosed(1, 1000)
    58. .parallel()
    59. .boxed()
    60. .collect(Collectors.toList());
    61. System.out.println(list.size());
    62. }

    5. 接口中的默认方法与静态方法

    6. 新时间日期APi

    7. 其他新特性