java8新特性之Stream
目录
流用过好多次了,每次都感叹它的简洁方便,这回抽时间总结一下。
一、什么是Stream?
Stream是Java8中用于简洁高效地操作集合类的工具,是对集合(Collection)对象功能的增强,比如对集合元素的遍历、过滤、转换等。使用Stream用户不需要关心如何遍历元素,只需要关心对每个集合元素进行什么操作。
二、获取Stream
- 从 Collection 和数组
- Collection.stream()
- Collection.parallelStream()
- Arrays.stream(T array) or Stream.of()
- 从 BufferedReader
- java.io.BufferedReader.lines()
- 静态工厂
- java.util.stream.IntStream.range()
- java.nio.file.Files.walk()
- 自己构建
- java.util.Spliterator
- 其它
- Random.ints()
- BitSet.stream()
- Pattern.splitAsStream(java.lang.CharSequence)
- JarFile.stream()
- 例子:
// 1. Individual values
Stream stream = Stream.of("a", "b", "c");
// 2. Arrays
String [] strArray = new String[] {"a", "b", "c"};
stream = Stream.of(strArray);
stream = Arrays.stream(strArray);
// 3. Collections
List<String> list = Arrays.asList(strArray);
stream = list.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());
Map<Long, UserDTO> map= stream.collect(Collectors.toMap(userDO::getUserId, userDO -> userDO));
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 3. String
String str = stream.collect(Collectors.joining()).toString();
四、Stream流处理
- forEach
//返回值为void,若要继续链式流处理,可以用peek
forEach(p -> System.out.println(p.getName()));
forEach(System.out::println)
- peek
//修改对象属性值
peek(i -> {
if(OtherMap.containsKey(i.getUserId())){
i.setPhone(OtherMap.get(i.getUserId()).getPhone());
i.setNickname(OtherMap.get(i.getUserId()).getNickname());
}
}).collect(Collectors.toList());
- filter
filter(n -> n%2 == 0)
filter(word -> word.length() > 0).
- map
map(String::toUpperCase)
map(n -> n * n)
- flatMap
- 1
//flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,
//最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。
Stream<List<Integer>> inputStream = Stream.of(
Arrays.asList(1),
Arrays.asList(2, 3),
Arrays.asList(4, 5, 6)
);
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());
+ 2 :同时获取对象中两个字段
list.stream().flatMap(a -> Stream.of(a.getNumber1(),a.getNumber2())).distinct()
- sorted
sorted((p1, p2) -> p1.getName().compareTo(p2.getName()))
distinct(去重)
limit、skip
//取前10个元素,之后再舍弃前3个,跳过前 3 个元素后只有后面 7 个返回
limit(10).skip(3)
- reduce
//第一个参数(空白字符)即为起始值,第二个参数(String::concat)为 BinaryOperator
reduce("", String::concat);
Stream.of(-1.5, 1.0, -3.0, -2.0).reduce(Double.MAX_VALUE, Double::min);
Stream.of(1, 2, 3, 4).reduce(0, Integer::sum);
Stream.of(1, 2, 3, 4).reduce(Integer::sum).get();
- collect
// 1. Collection
List<String> list1 = stream.collect(Collectors.toList());
List<String> list2 = stream.collect(Collectors.toCollection(ArrayList::new));
Set set1 = stream.collect(Collectors.toSet());
Map<Long, UserDTO> map= stream.collect(Collectors.toMap(userDO::getUserId, userDO -> userDO));
Stack stack1 = stream.collect(Collectors.toCollection(Stack::new));
// 2. String
String str = stream.collect(Collectors.joining()).toString();
- match
- Stream 有三个 match 方法,从语义上说:
- allMatch:Stream 中全部元素符合传入的 predicate,返回 true
- anyMatch:Stream 中只要有一个元素符合传入的 predicate,返回 true
- noneMatch:Stream 中没有一个元素符合传入的 predicate,返回 true
- 例:
- Stream 有三个 match 方法,从语义上说:
boolean isAllAdult = persons.stream().
allMatch(p -> p.getAge() > 18);
System.out.println("All are adult? " + isAllAdult);
boolean isThereAnyChild = persons.stream().
anyMatch(p -> p.getAge() < 12);
System.out.println("Any child? " + isThereAnyChild);
- min、max、count
五、进阶:自己生成流
- Stream.generate
通过实现 Supplier 接口,你可以自己来控制流的生成。这种情形通常用于随机数、常量的 Stream,或者需要前后元素间维持着某种状态信息的 Stream。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。由于它是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。
- 生成 10 个随机整数
Random seed = new Random();
Supplier<Integer> random = seed::nextInt;
Stream.generate(random).limit(10).forEach(System.out::println);
//Another way
IntStream.generate(() -> (int) (System.nanoTime() % 100)).
limit(10).forEach(System.out::println);
- 自实现 Supplier
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));
}
}
- Stream.iterate
iterate 跟 reduce 操作很像,接受一个种子值,和一个 UnaryOperator(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。
- 生成一个等差数列
Stream.iterate(0, n -> n + 3).limit(10). forEach(x -> System.out.print(x + " "));
//0 3 6 9 12 15 18 21 24 27
- 用 Collectors 来进行 reduction 操作:groupingBy/partitioningBy
- 按照年龄归组
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());
}
//输出:
//Age 0 = 2
//Age 1 = 2
//Age 5 = 2
//Age 8 = 1
//Age 9 = 1
//Age 11 = 2
- 按照未成年人和成年人归组:partitioningBy 其实是一种特殊的 groupingBy
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());
//输出:
//Children number: 23
//Adult number: 77
六、基本数值类型的流
- 对于基本数值型,目前有三种对应的包装类型 Stream:
IntStream、LongStream、DoubleStream。当然我们也可以用 Stream、Stream、Stream,但是 boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。
- 数据流的构造
IntStream.of(new int[]{1, 2, 3}).forEach(System.out::println);
IntStream.range(1, 3).forEach(System.out::println);
IntStream.rangeClosed(1, 3).forEach(System.out::println);