Java8 Stream新特性详解及实战java
在阅读Spring Boot源代码时,发现Java 8的新特性已经被普遍使用,若是再不学习Java8的新特性并灵活应用,你可能真的要out了。为此,针对Java8的新特性,会更新一系列的文章,欢迎你们持续关注。数据库
首先,咱们来看一下Spring Boot源代码ConfigFileApplicationListener类中的一段代码:编程
private List<Profile> getOtherActiveProfiles(Set<Profile> activatedViaProperty) { return Arrays.stream(this.environment.getActiveProfiles()).map(Profile::new) .filter((profile) -> !activatedViaProperty.contains(profile)) .collect(Collectors.toList()); }
这段代码怎么?够简洁明快吧,若是不使用Java8的新特性,想象一下得多少行代码才能实现?但若是没掌握或不了解Java8的新特性,这段代码读起来是否是很酸爽?数组
Java 8的API中新增了一个处理集合的抽象概念:Stream,中文称做“流”。它能够指定你但愿对集合进行的操做,能够执行很是复杂的查找、过滤和映射数据等操做。使用起来就像使用SQL语句来对数据库执行查询操做同样。可让你如行云流水通常写出简单、高效、干劲的代码。微信
Stream 中文称为 “流”,经过将集合转换为这么一种叫作 “流” 的元素序列(注意是抽象概念),经过声明性方式,可以对集合中的每一个元素进行一系列并行或串行的流水线操做。通俗来讲就是你只用告诉“流”你须要什么,便在出口处等待结果接口。dom
上图为Steam操做的基本流程,在后面的学习过程当中可反复与具体的代码进行对照,加深学习印象。ide
Stream操做的过程当中涉及到一些相关概念,先了解一下,方便后面统一称谓。函数
须要注意的是在整个操做的过程当中,聚合操做部分能够执行屡次操做,但每次操做并非像传统的集合遍历对集合里面的元素进行转换,而是将操做函数放入一个操做集合中,只有到最后一步(好比for-each打印)时才会一次性执行。性能
而流和迭代器相似,只能迭代一次。好比,当调用完collect方法以后,流便不能再使用了。学习
中间聚合操做:map (mapToInt, flatMap 等)、 filter、 distinct、 sorted、 peek、 skip、 parallel、 sequential、 unordered。
最终输出操做:forEach、 forEachOrdered、 toArray、 reduce、 collect、 min、 max、 count、iterator。
短路操做:anyMatch、 allMatch、 noneMatch、 findFirst、 findAny、 limit。
在 Java 8 中, 生成流有多种方法:Stream接口的静态工厂方法、集合提供的生成方法和其余特殊的生成方法。
Stream接口的静态工厂方法主要经过重载的of方法:
public static<T> Stream<T> of(T... values); public static<T> Stream<T> of(T t)
of方法,其生成的Stream是有限长度的,Stream的长度为其内的元素个数。使用示例代码:
Stream<String> stringStream = Stream.of("公众号"); Stream<String> stringsStream = Stream.of("关注","公众号", "程序新视界");
与of方法对应的generator方法生成的是无限长度的Stream,其元素是由Supplier接口提供的。
public static<T> Stream<T> generate(Supplier<T> s)
使用generate方法生成的Stream一般用于随机数和常量,或者须要先后元素间维持着某种状态信息的场景。把 Supplier 实例传递给 Stream.generate() 生成的 Stream,默认是串行(相对 parallel 而言)但无序的(相对 ordered 而言)。
示例代码以下:
Stream<Double> generateDouble = Stream.generate(Math::random); Stream<String> generateString = Stream.generate(new Supplier<String>() { @Override public String get() { return "公众号:程序新视界"; } });
其实上面两种写法的效果是同样的,只不过第一种采用了Lambda表达式,简化了代码。
iterate方法生成的也是无限长度的Stream,是经过函数f迭代对给指定的元素种子而产生无限连续有序Stream,其中包含的元素能够认为是:seed,f(seed),f(f(seed))无限循环。示例代码以下:
Stream.iterate(1,i -> i +1).limit(10).forEach(System.out::println);
打印结果为1,2,3,4,5,6,7,8,9,10。
上面的方法能够认为种子(seed)为1,f(seed)为在1的基础上“+1”,依次循环下去,直到达到limit的限制,最后生成对应的Stream。
empty方法生成一个空的Stream,不包含任何元素。
Collection接口和数组中都提供了默认的生成Stream的方法。直接看源代码:
// Collection中 default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } // 并行流操做 default Stream<E> parallelStream() { return StreamSupport.stream(spliterator(), true); } // Arrays中 public static <T> Stream<T> stream(T[] array) { return stream(array, 0, array.length); } public static <T> Stream<T> stream(T[] array, int startInclusive, int endExclusive) { return StreamSupport.stream(spliterator(array, startInclusive, endExclusive), false); }
示例代码:
List<String> list = new ArrayList<>(); list.add("欢迎关注"); list.add("微信公众号"); list.add("程序新视界"); list.stream().forEach(System.out::println); int[] nums = new int[]{1, 2, 3, 4, 5}; Arrays.stream(nums).forEach(System.out::println);
关于其余生成方法就不详细举例了,好比:Random.ints()、BitSet.stream()、JarFile.stream()、Pattern.splitAsStream(java.lang.CharSequence)、Files.lines(java.nio.file
.Path)等。
关于操做方法就不进行详细的讲解,更多的以示例的形式展现如何使用。
合并两个Stream。若是输入Stream有序,则新Stream有序;若是其中一个Stream为并行,则新Stream为并行;若是关闭新Stream,原Stream都将执行关闭。
Stream.concat(Stream.of("欢迎","关注"),Stream.of("程序新视界")).forEach(System.out::println);
Stream中元素去重。
Stream.of(1,1,1).distinct().forEach(System.out::println);
打印结果为1。
根据指定条件进行筛选过滤,留下知足条件的元素。
Stream.of(1, 2, 3, 4, 5).filter(i -> i >= 3).forEach(System.out::println);
打印结果为3,4,5。
将Stream中的元素进行映射转换,好比将“a”转为“A”,期间生产了新的Stream。同时为了提高效率,官方也提供了封装好的方法:mapToDouble,mapToInt,mapToLong。
Stream.of("a","b","c").map(item -> item.toUpperCase()).forEach(System.out::println); Stream.of("a","b","c").map(String::toUpperCase).forEach(System.out::println);
打印结果为A,B,C。
将流中的每个元素映射为一个流,再把每个流链接成为一个流。期间原有的Stream的元素会被逐一替换。官方提供了三种原始类型的变种方法:flatMapToInt,flatMapToLong和flatMapToDouble。
Stream.of(1, 2, 3).flatMap(i -> Stream.of(i * 10)).forEach(System.out::println);
打印结果为10,20,30。
生成一个相同的Stream,并提供一个消费函数,当新Stream中的元素被消费(执行操做)时,该消费函数会在此以前先执行。
Stream.of(1, 2).peek(i -> System.out.println("peekCall:" + i)).forEach(System.out::println);
打印结果依次为:peekCall:1,1,peekCall:2,2。
跳过前N个元素,取剩余元素,若是没有则为空Stream。
Stream.of(1, 2, 3).skip(2).forEach(System.out::println);
打印结果为3。
对Stream元素进行排序,可采用默认的sorted()方法进行排序,也可经过sorted(Comparator)方法自定义比较器来进行排序,前者默认调用equals方法来进行比较,
Stream.of(1, 3, 2).sorted().forEach(System.out::println);
打印结果:1,2,3。
限制返回前N个元素,与SQL中的limit类似。
Stream.of(1, 2, 3).limit(2).forEach(System.out::println);
打印结果为:1,2。
收集方法,实现了不少归约操做,好比将流转换成集合和聚合元素等。
Stream.of(1, 2, 3).collect(Collectors.toList()); Stream.of(1, 2, 3).collect(Collectors.toSet());
除了以上的集合转换,还有相似joining字符串拼接的方法,具体可查看Collectors中的实现。
返回Stream中元素个数。
Stream.of(1, 2, 3).count();
遍历Stream中全部元素。示例参考以上设计到的。
遍历Stream中全部元素,若是Stream设置了顺序,则按照顺序执行(Stream是无序的),默认为元素的插入顺序。
根据指定的比较器(Comparator),返回Stream中最大元素的Optional对象,Optional中的value即是最大值。
Optional能够表明一个值或不存在,主要是为了规避返回值为null,而抛出NullPointerException的问题,也是由Java8引入的。但当调用其get()方法时,若是当前值不存在则会抛出异常。
Optional<Integer> max = Stream.of(1, 2, 3).max(Comparator.comparingInt(o -> o)); System.out.println("max:" + max.get());
打印结果:max:3。
与max操做相同,功能相反,取最小值。
Optional<Integer> min = Stream.of(1, 2, 3).min(Comparator.comparingInt(o -> o)); System.out.println("min:" + min.get());
打印结果:min:1。
reduce可实现根据指定的规则从Stream中生成一个值,好比以前提到的count,max和min方法是由于经常使用而被归入标准库中。实际上,这些方法都是reduce的操做。
Stream.of(1, 2, 3).reduce(Integer::sum); Stream.of(1, 2, 3).reduce(0, (a, b) -> a + b);
以上两个方法都是对结果进行求和,不一样的是第一个方法调用的是reduce的reduce((T, T) -> T)方法,而第二个调用的是reduce(T, (T, T) -> T)。其中第二个方法的第一个参数0,表示从第0个值开始操做。
判断Stream中的全部元素是否知足指定条件。所有知足返回true,不然返回false。
boolean result = Stream.of(1, 2, 3).allMatch(i -> i > 0); System.out.println(result);
返回结果:true。
判断Stream中的元素至少有一个知足指定条件。若是至少有一个知足则返回true,不然返回false。
boolean anyResult = Stream.of(1, 2, 3).anyMatch(i -> i > 2); System.out.println(anyResult);
返回结果:true。
得到其中一个元素(使用stream()时找到的是第一个元素;使用parallelStream()并行时找到的是其中一个元素)。若是Stream为空,则返回一个为空的Optional。
Optional<String> any = Stream.of("A", "B", "C").findAny(); System.out.println(any.get());
返回结果:A。
得到第一个元素。若是Stream为空,则返回一个为空的Optional。
Optional<String> first = Stream.of("A", "B", "C").findFirst(); System.out.println(first.get());
返回结果:A。
判断Stream中是否全部元素都不知足指定条件。都不知足则返回true,不然false。
boolean noneMatch = Stream.of(1, 2, 3).noneMatch(i -> i > 5); System.out.println(noneMatch);
返回结果:true。
经过summaryStatistics方法可得到Stream的一些统计信息。
IntSummaryStatistics summaryStatistics = Stream.of(1, 2, 3).mapToInt((i) -> i).summaryStatistics(); System.out.println("max:" + summaryStatistics.getMax()); System.out.println("min:" + summaryStatistics.getMin()); System.out.println("sum:" + summaryStatistics.getSum()); System.out.println("average:" + summaryStatistics.getAverage());
这里用到了流与数值流直接的转换mapToInt,相似的方法还有mapToDouble、mapToLong。对应得到的数值流还提供了一些额外的方法,就像上面获取不一样统计信息的方法同样。
最后,咱们再来看一下Stream的主要接口类关系图。
其中,BaseStream定义了Stream的基本接口,Stream中定义了map、filter、flatMap等经常使用操做。IntStream、LongStream、DoubleStream是针对基本类型提供了便捷和特化操做。以上接口构建了Java8中流体系的根基。AbstractPipeline是流水线(Pipeline)的核心抽象类,用于构建和管理流水线。
另外,关于Stream的效率问题网上也有不少资料提到,下一篇文章,咱们将重点说说Stream的性能问题,关注公众号“程序新视界”,敬请期待。
原文连接:《Java8 Stream新特性详解及实战》