ThinkChat2.0新版上线,更智能更精彩,支持会话、画图、阅读、搜索等,送10W Token,即刻开启你的AI之旅 广告
[TOC] # 简介 jdk8出来的 ![](https://img.kancloud.cn/d7/ef/d7eff1c312ba8fc52b5171fbbd66742b_1129x565.png) 显示了过滤,映射,跳过,计数等多步操作,这是一种集合元素的处理方案,而方案是一种函数模型. 图中每一种方框都是流,调用指定方法,可以从一个流转换到另一个流. 这里的filter,map,skip都是对函数模型进行操作,集合元素并没有真正的处理,只有当终结方法count执行的时候,整个模型才会按照指定策略执行操作.这得益于lambda的延迟执行特性. 备注: stream流,其实是一个集合元素的函数模型,它并不是集合,也不是数据结构,其本身并不存储任何元素(或其他地址值) Stream(流)是一个来自数据源的元素队列 * 元素是特定类型的对象,形成一个队列.java中的stream并不会存储元素,而是按需计算. * **数据源** 流的来源.可以是集合,数组等 和collection不同,stream操作还有两个基础特性 * **pipelining**: 中间操作都会返回流对象本身.这样多个操作可以串联成一个管道,如同流式风格.这样做可以对操作进行优化,比如延迟支持和短路 * **内部迭代**: 以前对集合遍历都是通过Iterator或者增强for的方式,显示的在集合外部进行迭代,这叫做外部迭代.stream提供了内部迭代的方式,流可以直接调用遍历方法. # 获取流 `java.util.stream.Stream<T>`是java8新加入的最常用的流接口(这不是一个函数式接口) 获取一个流非常简单,有以下几种方式 * 所有的collection集合都可以通过stream默认方法获取流 * stream接口的静态方法of可以获取数组对应的流 ## 根据collection获取流 首先,`java.util.Collection`接口中加入了default方法stream用来获取流,所以其所有实现类均可获取流 ~~~ default Stream<E> stream() { return StreamSupport.stream(spliterator(), false); } ~~~ ## strem.parallel()或者parallelStream并行流 创建流时,除非另有说明,否则它始终是串行流。要创建并行流,请调用操作`Collection.parallelStream`或者,调用操作`BaseStream.parallel` ~~~ //切换为顺序流 sequential() ~~~ 单个个线程计算`1+2+3+...+1000=?`,java 8代码为: ~~~ public class DemoTest { public static void main(String[] args) { IntStream strem = IntStream.rangeClosed(1, 1000); int sum = strem.sum(); System.out.println(sum); } } ~~~ 引入并行计算,只需要添加`strem.parallel()`即可,具体如下: ~~~ public class DemoTest { public static void main(String[] args) { IntStream strem = IntStream.rangeClosed(1, 1000); int sum = strem.parallel().sum(); System.out.println(sum); } } ~~~ `java.util.Collection<E>`新添加了两个默认方法 * default Stream stream() : 返回串行流 * default Stream parallelStream() : 返回并行流 可以发现,stream()和parallelStream()方法返回的都是`java.util.stream.Stream<E>`类型的对象,说明它们在功能的使用上是没差别的。唯一的差别就是单线程和多线程的执行 ### 并行流的问题 发现一个查询返回一会是3,一会是4 ~~~ for (int i = 0; i < 100; i++) { List<String> list1 = new ArrayList<>(); List<String> list2 = new ArrayList<>(); list1.add("a"); list1.add("b"); list1.add("c"); list1.add("d"); list1.parallelStream().forEach(list -> list2.add(list)); System.out.println(list2.size()); } ~~~ 循环100次,会出现3,分析原因:ArrayList是线程不安全的,在并行流时,会出现并发问题。所以项目中不要动不动就用ArrayList,在高并发的情况下可能会有问题 **Arraylist本身底层是一个数组,多线程并发下线程并不安全,操作出现的原因无非就是多个线程赋值可能同时操作同一个地址,后赋值的把先赋值的给覆盖掉了,才会出现这种问题** ## 静态方法of获取 参数是一个可变参数,那么我们可以传递一个数组 ~~~ public static<T> Stream<T> of(T t) { return StreamSupport.stream(new Streams.StreamBuilderImpl<>(t), false); } ~~~ ~~~ //集合转为stream流 ArrayList<Object> list = new ArrayList<>(); Stream<Object> stream1 = list.stream(); HashSet<Object> set = new HashSet<>(); Stream<Object> stream2 = set.stream(); HashMap<Object, Object> map = new HashMap<>(); //获取键存到一个collection集合中 Set<Object> keySet = map.keySet(); Stream<Object> stream3 = keySet.stream(); //获取值存到coollection中 Collection<Object> values = map.values(); Stream<Object> stream4 = values.stream(); //获取键值对 Set<Map.Entry<Object, Object>> entries = map.entrySet(); Stream<Map.Entry<Object, Object>> stream5 = entries.stream(); //把数组转换为stream流 Stream<Integer> stream6 = Stream.of(1, 2, 3, 4, 5); //可变参数可以传递数组 Integer[] arr = {1, 2, 3, 4, 5, 6}; Stream<Integer> stream7 = Stream.of(arr); ~~~ ## 创建null的流 ~~~ Stream<Object> stream = Stream.ofNullable(null); stream.forEach(System.out::println); ~~~ ## 生成无限流 ~~~ 迭代 // public static<T> Stream<T> iterate(final T seed, final UnaryOperator<T> f) Stream<Integer> stream = Stream.iterate(0, x -> x + 2); stream.limit(10).forEach(System.out::println); // 生成 // public static<T> Stream<T> generate(Supplier<T> s) Stream<Double> stream1 = Stream.generate(Math::random); stream1.limit(10).forEach(System.out::println); ~~~ ## 组合concat ~~~ public static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) ~~~ 两个流合并为一个流,与`java.lang.String`中的concat方法不同 # 常用方法 ![](https://img.kancloud.cn/b3/2a/b32a79a25e8b89c5544e6f43344aa7f0_988x224.png) 这些方法分为两种: * 延迟方法: 返回值类型仍然是Stream接口自身类型的方法,因此支持链式调用 * 终结方法: 返回值类型不再是Stream接口自身类型的方法,因此不再支持类似StringBuilder那样的链式调用,终结方法包括: count和forEach方法 ## 终止方法 ### 逐一处理forEach stream流中的常用方法forEach ~~~ void forEach(Consumer<? super T> action); ~~~ Consumer接口是一个消费型函数式接口 forEach是用来遍历流中的数据 是一个终结方法,遍历之后就不能继续调用Stream流中的其他方法 ### 按照顺序取数据forEachOrdered ~~~ List<String> strs = Arrays.asList("a", "b", "c"); strs.stream().forEachOrdered(System.out::print);//abc System.out.println(); strs.stream().forEach(System.out::print);//abc System.out.println(); strs.parallelStream().forEachOrdered(System.out::print);//abc System.out.println(); strs.parallelStream().forEach(System.out::print);//bca ~~~ 先看第一段输出和第二段输出,使用的是stream的流,这个是一个串行流,也就是程序是串行执行的,所有看到遍历的结果都是按照集合的元素放入的顺序; 看第三段和第四段输出,使用的parallelStream的流,这个流表示一个并行流,也就是在程序内部迭代的时候,会帮你免费的并行处理,关于java8的并行处理,会在后期为大家介绍; 第三段代码的forEachOrdered表示严格按照顺序取数据,forEach在并行中,随机排列了;这个也可以看出来,在并行的程序中,如果对处理之后的数据,没有顺序的要求,使用forEach的效率,肯定是要更好的 ### 统计个数count ~~~ long count(); ~~~ 返回是long值不像之前的int值 ### 至少匹配一个元素anyMatch anyMatch方法可以回答“流中是否有一个元素能匹配给定的词” ~~~ List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); boolean in = words.stream().anyMatch((x) -> x.equals("In")); System.out.println(in); ~~~ ### 是否匹配所有元素allMatch allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的 ~~~ boolean isHealthy = menu.stream().allMatch(d -> d.getCalories() < 1000); ~~~ ### 没有任何元素与给定的匹配noneMatch ~~~ boolean isHealthy = menu.stream().noneMatch(d -> d.getCalories() >= 1000); ~~~ ### 查找元素findAny findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想 找到一道素食菜肴。你可以结合使用filter和findAny方法来实现这个查询: ~~~ Optional<Dish> dish = menu.stream().filter(Dish::isVegetarian) .findAny(); ~~~ ![](https://img.kancloud.cn/e4/f7/e4f7d83d3701122a60232454c8b144f5_663x90.png) findFirst是查找第一个 ### max,min ~~~ max(Comparator c) ~~~ 求 Stream 中的最大值、最小值。 例:取出 Stream 中最长的字符串 ~~~ String[] testStrings = { "java", "react", "angular", "javascript", "vue" }; Optional<String> max = Stream.of(testStrings).max((p1, p2) -> Integer.compare(p1.length(), p2.length())); System.out.println(max); ~~~ ### 收集collect ~~~ //将流转换为其他形式 collect(Collector c) ~~~ collect(toList()) 终止操作 由Stream中的值生成一个List列表,也可用collect(toSet())生成一个Set集合 ![](https://img.kancloud.cn/49/2f/492f2e2bedf0baa606c5142e4dbf6002_956x613.png) ~~~ String[] testStrings = { "java", "react", "angular", "vue" }; List<String> list = Stream.of(testStrings).collect(Collectors.toList()); for (int i = 0, length = list.size(); i < length; i++) { System.out.println(list.get(i)); } ~~~ ### 归约reduce ~~~ //可以将流中元素反复结合起来,得到一个值。返回 T reduce(T iden, BinaryOperator b) //可以将流中元素反复结合起来,得到一个值。返回 Optional<T> reduce(BinaryOperator b) ~~~ ## 中间操作 ### 去重distinct ~~~ @Test public void distinctStream() { Stream<String> distinctStream = Stream.of("bj","shanghai","tianjin","bj","shanghai").distinct(); Stream<String> sortedStream = distinctStream.sorted(Comparator.comparing(String::length)); sortedStream.forEach(x -> System.out.print(x + " ")); } ~~~ 输出 ~~~ bj tianjin shanghai ~~~ ### 排序sorted ~~~ Stream<Integer> sortedStream = Stream.of(1,3,7,4,5,8,6,2).sorted(); sortedStream.collect(Collectors.toList()).forEach(x -> System.out.print(x + " ")); System.out.println(); Stream<Integer> sortedReverseStream = Stream.of(1,3,7,4,5,8,6,2).sorted(new Comparator<Integer>() { @Override public int compare(Integer o1, Integer o2) { return o1 - o2; } }); Stream<Integer> sortedReverseStreamV2 = Stream.of(1,3,7,4,5,8,6,2).sorted((Integer o1, Integer o2) -> o2 - o1); sortedReverseStreamV2.collect(Collectors.toList()).forEach(x -> System.out.print(x + " ")); ~~~ 输出 ~~~ 1 2 3 4 5 6 7 8 8 7 6 5 4 3 2 1 ~~~ ### 过滤filter ~~~ Stream<T> filter(Predicate<? super T> predicate); ~~~ 把一个流转换为他的子集 ### 映射Map 把流中的元素映射到另一个 ~~~ <R> Stream<R> map(Function<? super T, ? extends R> mapper); ~~~ ### 扁平化flatmap flatmap可用 Stream 替换值,并将多个 Stream 流合并成一个 Stream 流 我们先来看个为什么要使用这个 ~~~ List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); List<String[]> list = words.stream() .map(word -> word.split("")).distinct().collect(toList()); ~~~ 这个方法的问题在于,传递给map方法的Lambda为每个单词返回了一个String\[\](String 列表)。因此,map返回的流实际上是Stream类型的。你真正想要的是用 Stream来表示一个字符流 ![](https://img.kancloud.cn/b0/fd/b0fdd1e540caeb1ccb363cebaced81ea_679x403.png) 尝试使用map和Arrays.stream() 首先,你需要一个字符流,而不是数组流。有一个叫作Arrays.stream()的方法可以接受 一个数组并产生一个流,例如: ~~~ String[] arrayOfWords = {"Goodbye", "World"}; Stream<String> streamOfwords = Arrays.stream(arrayOfWords); ~~~ 把它用在前面的那个流水线里,看看会发生什么: ![](https://img.kancloud.cn/c0/3b/c03bd7930f1c230b4637deae9c71a18c_481x120.png) 当前的解决方案仍然搞不定!这是因为,你现在得到的是一个流的列表(更准确地说是 Stream)!的确,你先是把每个单词转换成一个字母数组,然后把每个数组变成了一 个独立的流。 你可以像下面这样使用flatMap来解决这个问题 ~~~ List<String> words = Arrays.asList("Java 8", "Lambdas", "In", "Action"); List<String> collect = words.stream() .map(word -> word.split("")) .flatMap(Arrays::stream) .distinct() .collect(toList()); ~~~ 使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所 有使用`map(Arrays::stream)`时生成的单个流都被合并起来,即扁平化为一个流 ![](https://img.kancloud.cn/91/04/91047bc77f6b4965fd42d96eeef62d90_665x489.png) 一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接 起来成为一个流。 ![](https://img.kancloud.cn/6f/a1/6fa157a47291bd0c90a6d29446e618bf_1082x737.png) ### 取前几个limit ~~~ Stream<T> limit(long maxSize); ~~~ 参数是long,如果集合当前长度大于参数进行截取,否则不进行截取 ### 跳过前几个skip ~~~ Stream<T> skip(long n); ~~~ 如果流的长度大于n,则跳过n个.否则将会得到一个长度为0的空流 ### 排序sorted sorted有2个,无参和有参数(定制的) ~~~ //此时针对Employees进行排序:失败。原因:Employee类没有实现Comparable接口 // List<Employee> list1 = EmployeeData.getEmployees(); // list1.stream().sorted().forEach(System.out::println); // sorted(Comparator com)——定制排序 List<Employee> list1 = EmployeeData.getEmployees(); list1.stream().sorted((e1,e2) -> { if(e1.getAge() != e2.getAge()){ return e1.getAge() - e2.getAge(); }else{ return -Double.compare(e1.getSalary(),e2.getSalary()); } }).forEach(System.out::println); ~~~ ## 遇到不符合条件就终止talkWhile 从流中一直获取判定器为真的元素,一旦遇到元素为假,就终止处理,后面就不处理了 ~~~ Stream<Integer> stream = Stream.of(3, 9, 7, 1, 10, 11); Stream<Integer> integerStream = stream.takeWhile(t -> t % 2 != 0); integerStream.forEach(System.out::print); ~~~ 输出 3971 ## 遇到不合适的就丢弃dropWhile 只要为真的就一直丢弃,直到遇见为假的,就终止处理,后面就不处理了 ~~~ Stream<Integer> stream = Stream.of(3, 9, 7, 1, 6, 11); Stream<Integer> integerStream = stream.dropWhile(t -> t % 2 != 0); integerStream.forEach(System.out::print); ~~~ 输出 611