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/