上一篇系统学了方法引用的几种类型及应用场景,本篇开始咱们正式学习Stream。
Java8中的Stream与lambda表达式能够说是相伴相生的,经过Stream咱们能够更好的更为流畅更为语义化的操做集合。Stream api都位于java.util.stream包中。其中就包含了最核心的Stream接口,一个Stream实例能够串行或者并行操做一组元素序列,官方文档中给出了一个示例java
* <pre>{@code * int sum = widgets.stream()//建立一个流 * .filter(w -> w.getColor() == RED)//取出颜色是红色的元素 * .mapToInt(w -> w.getWeight())//返回每一个红色元素的重量 * .sum();//重量求和 * }</pre>
Java8中,全部的流操做会被组合到一个 stream pipeline中,这点相似linux中的pipeline概念,将多个简单操做链接在一块儿组成一个功能强大的操做。一个 stream pileline首先会有一个数据源,这个数据源多是数组、集合、生成器函数或是IO通道,流操做过程当中并不会修改源中的数据;而后还有零个或多个中间操做,每一个中间操做会将接收到的流转换成另外一个流(好比filter);最后还有一个终止操做,会生成一个最终结果(好比sum)。流是一种惰性操做,全部对源数据的计算只在终止操做被初始化的时候才会执行。linux
总结一下流操做由3部分组成
1.源
2.零个或多个中间操做
3.终止操做 (到这一步才会执行整个stream pipeline计算)segmentfault
建立流的几种方式api
//第一种 经过Stream接口的of静态方法建立一个流 Stream<String> stream = Stream.of("hello", "world", "helloworld"); //第二种 经过Arrays类的stream方法,实际上第一种of方法底层也是调用的Arrays.stream(values); String[] array = new String[]{"hello","world","helloworld"}; Stream<String> stream3 = Arrays.stream(array); //第三种 经过集合的stream方法,该方法是Collection接口的默认方法,全部集合都继承了该方法 Stream<String> stream2 = Arrays.asList("hello","world","helloworld").stream();
接下来咱们看一个简单的需求:将流中字符所有转成大写返回一个新的集合数组
List<String> list = Arrays.asList("hello", "world", "helloworld"); List<String> collect = list.stream().map(s -> s.toUpperCase()).collect(Collectors.toList());
这里咱们使用了Stream的map方法,map方法接收一个Function函数式接口实例,这里的map和Hadoop中的map概念彻底一致,对每一个元素进行映射处理。而后传入lambda表达式将每一个元素转换大写,经过collect方法将结果收集到ArrayList中。app
<R> Stream<R> map(Function<? super T, ? extends R> mapper);//map函数定义
那若是咱们想把结果放到Set中或者替他的集合容器,也能够这样dom
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toSet());//放到Set中
或者更为通用的ide
list.stream().map(s -> s.toUpperCase()).collect(Collectors.toCollection(TreeSet::new));//自定义容器类型
咱们能够本身制定结果容器的类型Collectors的toCollection接受一个Supplier函数式接口类型参数,能够直接使用构造方法引用的方式。函数
Stream中除了map方法对元素进行映射外,还有一个flatMap方法oop
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);
flatMap从方法命名上能够解释为扁平的map
map方法是将一个容器里的元素映射到另外一个容器中。
flatMap方法,能够将多个容器的元素所有映射到一个容器中,即为扁平的map。
看一个求每一个元素平方的例子
Stream<List<Integer>> listStream = Stream.of(Arrays.asList(1), Arrays.asList(2, 3), Arrays.asList(4, 5, 6)); List<Integer> collect1 = listStream.flatMap(theList -> theList.stream()). map(integer -> integer * integer).collect(Collectors.toList());
首先咱们建立了一个Stream对象,Stream中的每一个元素都是容器List<Integer>类型,并使用三个容器list初始化这个Stream对象,而后使用flatMap方法将每一个容器中的元素映射到一个容器中,这时flatMap接收的参数Funciton的泛型T就是List<Integer>类型,返回类型就是T对应的Stream。最后再对这个容器使用map方法求出买个元素的平方。
而后介绍一个用于获取统计信息的方法
//同时获取最大 最小 平均值等信息 List<Integer> list1 = Arrays.asList(1, 3, 5, 7, 9, 11); IntSummaryStatistics statistics = list1.stream().filter(integer -> integer > 2).mapToInt(i -> i * 2).skip(2).limit(2).summaryStatistics(); System.out.println(statistics.getMax());//18 System.out.println(statistics.getMin());//14 System.out.println(statistics.getAverage());//16
将list1中的数据取出大于2的,每一个数进行平方计算,skip(2)忽略前两个,limit(2)再取出前两个,summaryStatistics对取出的这两个数计算统计数据。mapToInt接收一个ToIntFunction类型,也就是接收一个参数返回值是int类型。
接下来看一下Stream中的一个静态方法,generate方法
/** * Returns an infinite sequential unordered stream where each element is * generated by the provided {@code Supplier}. This is suitable for * generating constant streams, streams of random elements, etc. * * @param <T> the type of stream elements * @param s the {@code Supplier} of generated elements * @return a new infinite sequential unordered {@code Stream} */ public static<T> Stream<T> generate(Supplier<T> s) { Objects.requireNonNull(s); return StreamSupport.stream( new StreamSpliterators.InfiniteSupplyingSpliterator.OfRef<>(Long.MAX_VALUE, s), false); }
generate接收一个Supplier,适合生成接二连三的流或者一个所有是随机数的流
Stream.generate(UUID.randomUUID()::toString).findFirst().ifPresent(System.out::println);
使用UUID.randomUUID()::toString 方法引用的方式建立了Supplier,而后取出第一个元素,这里的findFirst返回的是 Optional,由于流中有可能没有元素,为了不空指针,在使用前 ifPresent 进行是否存在的判断。
最后再学习一下另外一个静态方法,iterate
/** * Returns an infinite sequential ordered {@code Stream} produced by iterative * application of a function {@code f} to an initial element {@code seed}, * producing a {@code Stream} consisting of {@code seed}, {@code f(seed)}, * {@code f(f(seed))}, etc. * * <p>The first element (position {@code 0}) in the {@code Stream} will be * the provided {@code seed}. For {@code n > 0}, the element at position * {@code n}, will be the result of applying the function {@code f} to the * element at position {@code n - 1}. * * @param <T> the type of stream elements * @param seed the initial element * @param f a function to be applied to to the previous element to produce * a new element * @return a new sequential {@code Stream} */ public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) { Objects.requireNonNull(f); final Iterator<T> iterator = new Iterator<T>() { @SuppressWarnings("unchecked") T t = (T) Streams.NONE; @Override public boolean hasNext() { return true; } @Override public T next() { return t = (t == Streams.NONE) ? seed : f.apply(t); } }; return StreamSupport.stream(Spliterators.spliteratorUnknownSize( iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE), false); }
iterate方法有两个参数,第一个是seed也能够称做种子,第二个是一个UnaryOperator,UnaryOperator其实是Function的一个子接口,和Funciton区别就是参数和返回类型都是同一种类型
@FunctionalInterface public interface UnaryOperator<T> extends Function<T, T> { }
iterate方法第一次生成的元素是UnaryOperator对seed执行apply后的返回值,以后全部生成的元素都是UnaryOperator对上一个apply的返回值再执行apply,不断循环。
f(f(f(f(f(f(n))))))......
//从1开始,每一个元素比前一个元素大2,最多生成10个元素 Stream.iterate(1,item -> item + 2).limit(10).forEach(System.out::println);
咱们在使用stream api时也要注意一些陷阱,好比下面这个例子
//Stream陷阱 distinct()会一直等待产生的结果去重,将distinct()和limit(6)调换位置,先限制结果集再去重就能够了 IntStream.iterate(0,i -> (i + 1) % 2).distinct().limit(6).forEach(System.out::println);
若是distinct()一直等待那程序会一直执行不断生成数据,因此须要先限制结果集再去进行去重操做就能够了。