Steam API 是 Java 8 中全新的特性,它基于Lambda 表达式,对集合(Collection)的各类操做进行了大幅度的加强,极大的提升了编码的效率和程序的可读性。java
Stream API 提供串行和并行两种模式,并行模式会自动建立多个线程,使用 fork/join(Java 7 特性) 并行方式来拆分任务和加速处理过程,能充分利用多核处理器。牛X的是,咱们并不须要手动写多线程处理的代码,Stream API 自行实现了高性能的并发程序。数组
eg,看一下下面这段代码干了什么?而后想一下若是不使用 Stream 操做,应该怎么实现?安全
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); zoneIds.stream().filter(zoneId -> zoneId.startsWith("Asia")).skip(10).limit(5).forEach(System.out::println);
这里的 Stream 跟 IO流等其它流并非指一样的东西,而是对集合执行流式操做的一种抽象。要分清一点,Stream 不是数据结构,自己不存储元素,元素可能存储在底层的集合中,或根据须要产生出来。数据结构
Stream API 的用法很简单,分3步执行,即: 建立/获取流 -> 中间操做(过滤、转换等) -> 终止操做( 聚合、收集结果)多线程
其中, 中间操做能够执行屡次,而且都是延迟执行的,每次操做原有的 Stream 对象不改变,会产生一个新的 Stream; 终止操做只能有一个,一旦执行完成,Stream 就没了。并发
Java 8 中,Collection 接口增长了 stream 方法,因此咱们能够把任何一个集合转成Streamdom
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); Stream<String> demo = zoneIds.stream();
Arrays 类中增长了 stream(T[] array)方法ide
String[] arr = {"1"}; Stream<String> demo = Arrays.stream(arr);
Stream.of(T... values) Stream.generate(Supplier<T> s) 生成无限流 Stream.iterate(final T seed, final UnaryOperator<T> f), 生成无限流,可是多了一个seed,生成的流是 seed, f(seed), f(f(seed))...函数
Stream.of("1", "2", "2", "5", "7", "5", "6", "3", "4", "3"); Stream<Integer> stream1 = Stream.generate(() -> RandomUtils.nextInt(1, 10)); stream1.limit(10).forEach(System.out::print); // 输出 2223915594 Stream<Integer> stream2 = Stream.iterate(1, x -> x + 1); stream2.limit(10).forEach(System.out::print); // 12345678910
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); Stream<String> oldStream = zoneIds.stream(); Stream<String> newStream = oldStream.filter(zoneId -> zoneId.startsWith("Asia"));
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); Stream<String> oldStream = zoneIds.stream(); Stream<String> newStream = oldStream.map(zoneId -> zoneId.replace("//", "-"));
获取一个流的一部分。工具
Set<String> zoneIds = ZoneId.getAvailableZoneIds(); zoneIds.stream().skip(10).limit(5); // 另外一个较好的例子 Stream<Integer> limit = Stream.generate(() -> RandomUtils.nextInt(1, 10)).limit(100);
将两个流合并成一个
Stream.concat(Stream.of("1", "2"), Stream.of("4", "5", "6"));
Stream.of("1", "2", "2", "5", "7", "5", "6", "3", "4", "3") .peek(x -> System.out.println(x+" ")) .filter(x -> x.compareTo("3")>0) .map(x -> x.replace("5", "9")) .distinct() .sorted() .skip(1) .limit(5) .forEach(System.out::print);
最终结果:
1 2 2 5 7 5 6 3 4 3 679
思考一个疑问,咱们对一个 Stream 进行屡次转换操做,每次都会产生一个新的 Stream,这样操做是否是比咱们本身写单次循环耗费更多的时间?
不是,转换操做都是lazy的,多个转换操做只会在终止操做的时候融合起来,一次循环完成。
这部分是 Stream API 中最重要的内容,从上文中咱们已经知道如何建立一个流以及如何对其进行转换。如今,咱们要从流中获取结果。
IntStream is = IntStream.of(1, 2, 3 , 4, 5); System.out.println(is.reduce((a, b) -> a+b)); // 输出 OptionalInt[15]
在这个操做里能够看到,reduce 方法有两个参数。其中第1个参数是上次操做返回的结果,第2个参数是 Stream 中下一个元素。若是是第一次执行,第1个参数是 Stream 中的第一个元素, 第2个参数是Stream 中的第2个元素。简单描述一下这个操做的执行过程,
操做 | 结果 |
---|---|
首先流初始化后有5个元素 | 1, 2, 3, 4, 5 |
第1次聚合 | 3,3, 4, 5 |
第2次聚合 | 6, 4, 5 |
第3次聚合 | 10, 5 |
第4次聚合 | 15 |
reduce方法还有一个很经常使用的变种,接收两个参数: T reduce(T identity, BinaryOperator<T> accumulator),相对上面已经介绍过方法的多了一个参数:它容许用户提供一个值,若是Stream为空,就直接返回该值。
IntStream is = IntStream.of(); System.out.println(is.reduce(5, (a, b) -> a+b)); // 输出 5
不少状况下,咱们所作的聚合操做都是同样的。因此为了方便,Stream API 直接提供了经常使用的聚合方法
IntStream s = IntStream.of(1,2,3,4); System.out.println(s.sum());
注:只有 IntStream、LongStream、DoubleStream 才有 sum 方法
System.out.println(ZoneId.getAvailableZoneIds().stream().count()); // 输出 590 System.out.println(ZoneId.getAvailableZoneIds().stream().min(String::compareTo)); // 输出 Optional[Africa/Abidjan] System.out.println(ZoneId.getAvailableZoneIds().stream().max(String::compareTo)); // 输出 Optional[Zulu]
System.out.println(ZoneId.getAvailableZoneIds().stream().findFirst()); // Optional[Asia/Aden] System.out.println(ZoneId.getAvailableZoneIds().stream().findAny()); // Optional[Asia/Aden]
System.out.println(ZoneId.getAvailableZoneIds().stream().allMatch(x -> x.startsWith("A"))); // false System.out.println(ZoneId.getAvailableZoneIds().stream().anyMatch(x -> x.startsWith("A"))); // true System.out.println(ZoneId.getAvailableZoneIds().stream().noneMatch(x -> x.startsWith("A"))); // false
<R> R collect(Supplier<R> supplier, BiConsumer<R, ? super T> accumulator, BiConsumer<R, R> combiner);
可是这个方法用着比较麻烦, 通常直接用另外的工具方法
List<String> list = ZoneId.getAvailableZoneIds().stream().limit(5).collect(Collectors.toList()); Set<String> set = ZoneId.getAvailableZoneIds().stream().collect(Collectors.toSet());
Map<String, String> map = ZoneId.getAvailableZoneIds().stream() .limit(3) .collect(Collectors.toMap(x -> x, y -> y)); System.out.println(map); // 输出 {Asia/Aden=Asia/Aden, America/Cuiaba=America/Cuiaba, Etc/GMT+9=Etc/GMT+9}
并非全部时候咱们都须要把结果汇聚在一块儿,咱们也能够只是遍历各个元素
Stream<String> zoneIdStream = ZoneId.getAvailableZoneIds().stream(); Iterator<String> it = zoneIdStream.iterator();
ZoneId.getAvailableZoneIds().stream().limit(5).forEach(System.out::print);
public static <T, K, D, A, M extends Map<K, D>> Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier, Supplier<M> mapFactory, Collector<? super T, A, D> downstream)
最简单的用法是只提供一个分组函数,那么默认会返回
Map<String, List<String>> map = ZoneId.getAvailableZoneIds().stream() .limit(5) .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/"))) ); System.out.println(map); // 输出 {Etc=[Etc/GMT+9, Etc/GMT+8], Asia=[Asia/Aden], Africa=[Africa/Nairobi], America=[America/Cuiaba]}
若是咱们想控制类型,能够用Collectors改变,好比
Map<String, Set<String>> map = ZoneId.getAvailableZoneIds().stream() .limit(5) .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/")), Collectors.toSet()) );
Stream Api 还提供其余一些收集器,好比counting,计算每一个分组的元素数量
Map<String, Long> map = ZoneId.getAvailableZoneIds().stream() .limit(5) .collect(Collectors.groupingBy(x -> x.substring(0, x.indexOf("/")), Collectors.counting())); System.out.println(map); // 输出 {Etc=2, Asia=1, Africa=1, America=1}
在前文的介绍中,你应该看到了 Optional 这个东西,有不少终止操做的结果是Optional<T>。那么,Optional是什么? Optional 是一个容器,它可能包含也可能不包含一个值,用意是替代不安全的返回类型 NULL。好比,咱们常见的一种代码,
public Integer getInteger() { return null; }
若是咱们须要使用这个方法,通常是这样的
Integer i = getSomething(); if(i != null) { System.out.println(i); // do something }
调用一个返回T类型的方法后,为了安全咱们必须验证这个对象不是 NULL,形成代码的阅读星和维护性都比较差,可是若是没有这样作,就极可能出现 NullPointException。 Optional 就是为了解决这个问题而设计的,它提供了一个接受 Consumer<? super T>类型参数的方法 ifPresent,只有当存在可用的元素时,才执行逻辑
public Optional<Integer> getSomethingOptinal() { return Optional.empty(); }
咱们直接调用这个方法
getSomethingOptinal().ifPresent(System.out::print);
正常执行,没有异常。 因此如今当咱们一个方法返回List集合时,应该老是返回一个空的List,而不是Null; 当一个返回T类型的方法有可能为Null 时,应该该用Optional<T>。