• jdk8-lambda-stream的使用


    1, 认识stream(声明式编程)

    Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator, 原始版本的Iterator,用户只能一个一个的遍历元素并对其执行某些操作;高级版本的Stream,用户只要给出需要对其包含的元素执行什么操作,比如“过滤掉长度大于10的字符串”、“获取每个字符串的首字母”等,具体这些操作如何应用到每个元素上,就给Stream就好了!

    Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程

    2, 使用stream的基本过程

    1, 创建Stream;
    2, 转换Stream,每次转换原有Stream对象不改变,返回一个新的Stream对象(**可以有多次转换**);
    3, 对Stream进行聚合(Reduce)操作,获取想要的结果;

    3, 创建stream

    1), 使用stream静态方法创建

    @Test
    public void test() {
    // of
    Stream<Integer> integerStream = Stream.of(1, 2, 3, 5);
    // generate, 无限长度, 懒加载, 类似工厂, 使用必须指定长度
    Stream<Double> generate = Stream.generate(Math::random);
    // iterator方法, 无限长度,
    Stream.iterate(1, item -> item + 1).limit(10).forEach(System.out::print);
    }

    2), 通过collection的子类生成

    • Collection.stream()
    • Collection.parallelStream()
    • Arrays.stream(T array) or Stream.of()
        @Test
        public void test2() {
            List<Integer> integers = Arrays.asList(1, 2, 3, 4, 5, 6);
            Stream<Integer> stream = integers.stream();
        }

    3), buffer生成 (通过实现 Supplier 接口)

    • java.io.BufferedReader.lines()
    • Pattern.splitAsStream(java.lang.CharSequence)
    • java.util.stream.IntStream.range()

    4), 自定义supplier接口

        @Test
        public void test13() {
            Stream.generate(new PersonSupplier()).
                    limit(10).
                    forEach(p -> System.out.println(p.getName() + ", " + p.getAge()));
    
        }
        private class PersonSupplier implements Supplier<Person> {
            private int index = 0;
            private Random random = new Random();
            @Override
            public Person get() {
                return new Person(index++, "StormTestUser" + index, random.nextInt(100));
            }
        }

    流的主要操作( https://www.ibm.com/developerworks/cn/java/j-lo-java8streamapi/index.html ) 

    Intermediate:一个流可以后面跟随零个或多个 intermediate 操作。其目的主要是打开流,做出某种程度的数据映射/过滤,然后返回一个新的流,交给下一个操作使用。这类操作都是惰性化的(lazy),就是说,仅仅调用到这类方法,并没有真正开始流的遍历。
    Terminal:一个流只能有一个 terminal 操作,当这个操作执行后,流就被使用“光”了,无法再被操作。所以这必定是流的最后一个操作。Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果,或者一个 side effect。

    分类操作

    Intermediate:
      map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 limit、 skip、 parallel、 sequential、 unordered
    Terminal:
      forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、 anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 iterator
    Short-circuiting:
      anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit

    4, 转换stream

    每次使用的本质, 是创建了一个新的stream, 旧的stream保持不变

    1. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
    2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;
    3. map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。
        有 mapToInt, mapToLong, mapToDouble
        直接转换为响应的类型, 避免拆装箱的消耗
    4. flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
    5. peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;
    6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
    7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
    8. range: 截取
    9, sorted: 排序

    排序使用: 

        @Test
        public void test11() {
            List<String> list = Arrays.asList("2", "5", "2", "1", "8", "4", "3", "7", "9");
            List<String> list2 = list.stream().distinct().sorted((o1, o2) -> (Integer.parseInt(o2) - Integer.valueOf(o1))).collect(Collectors.toList());
            System.out.println(list2);
        }

    综合:

        @Test
        public void test3() {
            List<Integer> integers = Arrays.asList(1, 1, 3 , null, 2, null, 3, 4, 5, 8, 10);
            System.out.println(integers.stream().filter(num -> num != null)
                    .distinct()
                    .mapToInt(num -> num * 10)
                    .peek(System.out::println).skip(2).limit(4).sum());
        }

    关于多次stream的性能问题:

    转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。

    流转换其他数据结构

    // 1. Array
    String[] strArray1 = stream.toArray(String[]::new);
    // 2. Collection
    List<String> list1 = stream.collect(Collectors.toList());
    List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
    Set set1 = stream.collect(Collectors.toSet());
    Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
    // 3. String
    String str = stream.collect(Collectors.joining()).toString();

    5, 汇聚操作

    汇聚操作(也称为折叠)接受一个元素序列为输入,反复使用某个合并操作,把序列中的元素合并成一个汇总的结果。比如查找一个数字列表的总和或者最大值,或者把这些数字累积成一个List对象。Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()

    1), 可变汇聚, collect, 把输入的元素们累积到一个可变的容器中,比如Collection或者StringBuilder;

    <R> R collect(Supplier<R> supplier, <R, ? super T> accumulator, <R, R> combiner);

    Supplier supplier是一个工厂函数,用来生成一个新的容器;

    BiConsumer accumulator也是一个函数,用来把Stream中的元素添加到结果容器中;

    BiConsumer combiner还是一个函数,用来把中间状态的多个结果容器合并成为一个(并发的时候会用到)

        @Test
        public void test4() {
            List<Integer> nums = Arrays.asList(1, 1, 3 , null, 2, null, 3, 4, 5, 8, 10);
            List<Integer> numsWithCollect = nums.stream().filter(num -> num != null)
                    .collect(() -> new ArrayList<Integer>(),
                            (list, item) -> list.add(item),
                            (list1, list2) -> list1.addAll(list2));
            System.out.println(numsWithCollect);
        }

    太繁琐了, 在jdk8 中提供了Collectors工具类, 可以直接实现汇聚( http://docs.oracle.com/javase/8/docs/api/java/util/stream/Collectors.html )

        @Test
        public void test5() {
            List<Integer> nums = Arrays.asList(1, 1, 3 , null, 2, null, 3, 4, 5, 8, 10);
            List<Integer> collect = nums.stream().filter(num -> num != null)
                    .collect(Collectors.toList());
            System.out.println(collect);
        }

    ps: collectos中提供了大量的方法, 粘贴一段api开头的方法

        // Accumulate names into a List
         List<String> list = people.stream().map(Person::getName).collect(Collectors.toList());
    
         // Accumulate names into a TreeSet
         Set<String> set = people.stream().map(Person::getName).collect(Collectors.toCollection(TreeSet::new));
    
         // Convert elements to strings and concatenate them, separated by commas
         String joined = things.stream()
                               .map(Object::toString)
                               .collect(Collectors.joining(", "));
    
         // Compute sum of salaries of employee
         int total = employees.stream()
                              .collect(Collectors.summingInt(Employee::getSalary)));
    
         // Group employees by department
         Map<Department, List<Employee>> byDept
             = employees.stream()
                        .collect(Collectors.groupingBy(Employee::getDepartment));
    
         // Compute sum of salaries by department
         Map<Department, Integer> totalByDept
             = employees.stream()
                        .collect(Collectors.groupingBy(Employee::getDepartment,
                                                       Collectors.summingInt(Employee::getSalary)));
    
         // Partition students into passing and failing
         Map<Boolean, List<Student>> passingFailing =
             students.stream()
                     .collect(Collectors.partitioningBy(s -> s.getGrade() >= PASS_THRESHOLD));

    2) reduce汇聚

        @Test
        public void test6() {
            List<Integer> nums = Arrays.asList(1, 1, 3 , null, 2, null, 3, 4, 5, 8, 10);
            Integer count = nums.stream().filter(num -> num != null)
                    .reduce((sum, num) -> sum + num).get();
            System.out.println(count);
        }

    可以看到reduce方法接受一个函数,这个函数有两个参数,第一个参数是上次函数执行的返回值(也称为中间结果),第二个参数是stream中的元素,这个函数把这两个值相加,得到的和会被赋值给下次执行这个函数的第一个参数。要注意的是:**第一次执行的时候第一个参数的值是Stream的第一个元素,第二个参数是Stream的第二个元素**。这个方法返回值类型是Optional,这是Java8防止出现NPE的一种可行方法,后面的文章会详细介绍,这里就简单的认为是一个容器,其中可能会包含0个或者1个对象。

    可以提供一个初始值, 如果ints为空则直接返回默认值

    List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    System.out.println("ints sum is:" + ints.stream().reduce(0, (sum, item) -> sum + item));

    count()

    List<Integer> ints = Lists.newArrayList(1,2,3,4,5,6,7,8,9,10);
    System.out.println("ints sum is:" + ints.stream().count());

    match()

        @Test
        public void test7() {
            List<Integer> nums = Arrays.asList(1, 1, 3 , null, 2, null, 3, 4, 5, 8, 10);
            System.out.println(nums.stream().filter(num -> num != null).allMatch(num -> num < 8));
        }

    max(), min()

        @Test
        public void test12() throws IOException {
            BufferedReader br = new BufferedReader(new FileReader("d:\test.log"));
            int longest = br.lines().
                    mapToInt(String::length).
                    max().
                    getAsInt();
            br.close();
            System.out.println(longest);
        }

    – allMatch:是不是Stream中的所有元素都满足给定的匹配条件
    – anyMatch:Stream中是否存在任何一个元素满足匹配条件

    -  noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true

    – findFirst: 返回Stream中的第一个元素,如果Stream为空,返回空Optional
    – noneMatch:是不是Stream中的所有元素都不满足给定的匹配条件
    – max和min:使用给定的比较器(Operator),返回Stream中的最大|

     3) 分组

        按年龄分组

        
    @Test
    public void test13() { Map<Integer, List<Person>> personGroups = Stream.generate(new PersonSupplier()). limit(100). collect(Collectors.groupingBy(Person::getAge)); Iterator it = personGroups.entrySet().iterator(); while (it.hasNext()) { Map.Entry<Integer, List<Person>> persons = (Map.Entry) it.next(); System.out.println("Age " + persons.getKey() + " = " + persons.getValue().size()); } }

      按是否成年分组

    @Test
    public void test13() {
    Map<Boolean, List<Person>> children = Stream.generate(new PersonSupplier()).
    limit(100).
    collect(Collectors.partitioningBy(p -> p.getAge() < 18));
    System.out.println("Children number: " + children.get(true).size());
    System.out.println("Adult number: " + children.get(false).size());
    }

    一个综合运用的例子: 

    MongoClient client = getMongoClient();
            MongoDatabase mongoDatabase = client.getDatabase(Constance.database());
            MongoCollection<Document> collection = mongoDatabase.getCollection(Constance.collection());
    
            MongoIterable<TopicMacEntity> iter = collection.find().map(document -> {
                String topic = document.get("topic").toString().toUpperCase();
                String mac = document.get("mac").toString().toUpperCase();
                return new TopicMacEntity(topic, mac);
            });
            Map<String, List<TopicMacEntity>> collect = Lists.newArrayList(iter).stream().collect(Collectors.groupingBy(TopicMacEntity::topic));
            Map<String, List<String>> resultMap = collect.entrySet().stream()
                    .collect(Collectors.toMap(Map.Entry::getKey, v -> v.getValue().stream().map(t -> t.mac()).collect(Collectors.toList())));
            return resultMap;

     更多grouping的强大用法: 

    http://developer.51cto.com/art/201404/435431.htm

    原博客: 

    http://ifeve.com/stream/

  • 相关阅读:
    资源放送丨《Oracle存储过程中的性能瓶颈点》PPT&视频
    警示:一个update语句引起大量gc等待和业务卡顿
    周末直播丨细致入微
    Java VS Python:哪个未来发展更好?
    【LeetCode】279.完全平方数(四种方法,不怕不会!开拓思维)
    事件驱动
    Android初级教程以动画的形式弹出窗体
    Android简易实战教程--第五话《开发一键锁屏应用》
    迎战大数据-Oracle篇
    Android初级教程获取手机位置信息GPS与动态获取最佳方式
  • 原文地址:https://www.cnblogs.com/wenbronk/p/7345450.html
Copyright © 2020-2023  润新知