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

 

6. sorted 排序

//返回 对象集合以类属性一升序排序
list.stream().sorted(Comparator.comparing(类::属性一));
 
//返回 对象集合以类属性一降序排序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed());//先以属性一升序,结果进行属性一降序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()));//以属性一降序
 
//返回 对象集合以类属性一升序 属性二升序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二));
 
//返回 对象集合以类属性一降序 属性二升序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二));//先以属性一升序,升序结果进行属性一降序,再进行属性二升序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二));//先以属性一降序,再进行属性二升序
 
//返回 对象集合以类属性一降序 属性二降序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,升序结果进行属性一降序,再进行属性二降序
list.stream().sorted(Comparator.comparing(类::属性一,Comparator.reverseOrder()).thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一降序,再进行属性二降序
 
//返回 对象集合以类属性一升序 属性二降序 注意两种写法
list.stream().sorted(Comparator.comparing(类::属性一).reversed().thenComparing(类::属性二).reversed());//先以属性一升序,升序结果进行属性一降序,再进行属性二升序,结果进行属性一降序属性二降序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(类::属性二,Comparator.reverseOrder()));//先以属性一升序,再进行属性二降序

//空/Null数据排序
list.stream().sorted(Comparator.comparing(类::属性一).thenComparing(item -> item.属性二, Comparator.nullsLast(Date::compareTo))).collect(Collectors.toList());

//空/Null数据分组
Map<String, List<类>> map = list.stream().collect(Collectors.groupingBy(item -> {
  if (item.属性一 == null || item.属性一.equals("")) {
    return "";
  }
  return DateFormat.getDateInstance().format(item.属性一);
}))

 

 

原博客: 

http://ifeve.com/stream/

posted @ 2017-08-11 14:08  bronk  阅读(1136)  评论(0编辑  收藏  举报