Java8 Stream API详细操作 ╰+哭是因爲堅強的太久メ 2022-12-02 13:49 235阅读 0赞 ### 一、Stream流的基本概念 ### **1、什么是Stream流?** 流是Java8引入的全新概念,它用来处理集合中的数据,对集合(Collection)对象功能的增强,专注于对集合对象进行各种非常便利、高效的聚合操作(aggregate operation),或者大批量数据操作 (bulk data operation)。 Java中的Stream并不会存储元素,而是按需计算,且只能遍历一次。同时结合 Lambda 表达式,极大的提高编程效率和程序可读性。 **Stream流采用内部迭代方式,通过访问者模式(Visitor)实现。** 若要对集合进行处理,则需我们手写处理代码,这就叫做外部迭代。比如:使用Iterator或for循环来遍历集合。 若要对流进行处理,我们只需告诉流我们需要什么结果,处理过程由流自行完成,这就称为内部迭代。比如:Collection.forEach(…)方法迭代。 **2、Stream流的操作种类** 流的操作可以分为两种:中间操作和终端操作。 **1. 中间操作** 当数据源中的数据上了流水线后,这个过程对数据进行的所有操作都称为 Pipelining 操作。 中间操作可以连接起来,将一个流转换为另一个流。这样多个操作可以串联成一个管道, 如同流式风格(fluent style)。 中间操作都会返回流对象本身。 **2. 终端操作 ** 当所有的中间操作完成后,若要将数据从流水线上拿下来,则需要执行终端操作。 一个流只能有一个 Terminal 操作,当这个操作执行后,流的操作就结束了,无法再被操作。 Terminal 操作的执行,才会真正开始流的遍历,并且会生成一个结果。 **3、Stream流的操作过程** Stream流操作可以分三个步骤: > 1、获取一个数据源:可以是集合,数组,I/O channel, 产生器generator 等。 > > 2、中间操作(intermediate operation): > > 在管道的节点上进行处理,比如: filter、 distinct、 sorted等,它们可以串连起来形成流水线。 > > 3、最终操作(terminal operation):执行终端操作后本次流结束,并返回一个执行结果。 > > 注意:流进行了终止操作后,不能再次使用,否则会报错。 ### 二、获取Stream流对象 ### 在使用流之前,需要先拥有一个数据源,并通过StreamAPI提供的一些方法获取该数据源的Stearm接口类型流对象。 注意: * 除了Stream类型外,还有几个IntStream、LongStream、DoubleStream很常用, 因为boxing 和 unboxing 会很耗时,所以特别为这三种基本数值型提供了对应的 Stream。 * 此外还有一个流类型:并行流parallelStream,parallelStream提供了流的并行处理,它是Stream的另一重要特性,其底层使用Fork/Join框架实现。 **串行流:**适合存在线程安全问题、阻塞任务、重量级任务,以及需要使用同一事务的逻辑。 Stearm类型有:Stream,还有IntStream、LongStream、DoubleStream也很常用,因boxing和 unboxing会很耗时,所以这三种基本数值型提供了对应的 Stream。 **并行流:**适合没有线程安全问题、较单纯的数据处理任务。 Stearm类型有:并行流parallelStream。 parallelStream提供了流的并行处理,其中 parallelStream()方法能够充分利用多核CPU的优势,使用多线程加快对集合数据的处理速度。 下面来认识常用的数据源来获取Stearm对象。后面进行操作的更多方法等,请查看API。函数式接口就不多说了。 public class User { private int id; private String name; private String sex; private int age; private int height; ... } **1、集合 ** 集合 数据源比较为常用,通过 stream()方法即可获取 Stearm对象。 List<User> list = new ArrayList<>(); list.add(new User(1, "赵云", "男", 18, 178)); list.add(new User(2, "后裔", "男", 20, 188)); list.add(new User(1, "妲己", "女", 17, 175)); Stream<User> stream = list.stream(); Stream<User> parallelStream = list.parallelStream(); **2、数组 ** 通过 Arrays 类提供的静态 stream()方法获取数组的 Stearm对象。 String[] arr = new String[]{"赵云", "后裔", "妲己"}; Stream<String> stream = Arrays.stream(arr); **3、值 ** 直接将几个值变成 Stearm对象,IntStream、LongStream、DoubleStream。 IntStream intStream = IntStream.of(new int[]{1, 2, 3}); LongStream longStream = LongStream.range(1, 3); // [1,3) LongStream longStream2 = LongStream.rangeClosed(1, 3); // [1,3] DoubleStream doubleStream = DoubleStream.of(1.0, 3.0); **4、其他创建方法** * * <table> <tbody> <tr> <td><code>static <T> <a rel="nofollow">Stream</a><T></code></td> <td><code><a rel="nofollow">generate</a>(<a rel="nofollow">Supplier</a><T> s)</code> <p>返回一个无穷序列无序流,其中每个元素是由提供 <code>Supplier</code>生成。</p> </td> </tr> <tr> <td><code>static <T> <a rel="nofollow">Stream</a><T></code></td> <td><code><a rel="nofollow">iterate</a>(T seed, <a rel="nofollow">UnaryOperator</a><T> f)</code> <p>返回一个无穷序列有序 <code>Stream</code>由最初的一元 <code>seed</code>函数的 <code>f</code>迭代应用产生的,产生一个由 <code>seed</code>, <code>f(seed)</code>, <code>f(f(seed))</code> <code>Stream</code>,等。</p> </td> </tr> </tbody> </table> Stream中的generate() 很好理解。 iterate () 方法,参数为一个种子值,和一个一元操作符(例如 f)。然后种子值成为 Stream 的第一个元素,f(seed) 为第二个,f(f(seed)) 第三个,以此类推。 注意:这两种方法创建的流是无限的,在管道中,必须利用 limit 之类的操作限制 Stream 大小。 // 生成 10 个随机整数 Stream<Integer> stream = Stream.generate(new Random()::nextInt).limit(10); // 生成10个等差(2)数列 Stream<Integer> stream1 = Stream.iterate(0, n -> n + 2).limit(10); ### 三、中间操作 ### **1、filter方法:过滤掉不符合谓词判断的数据** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">filter</a>(<a rel="nofollow">Predicate</a><? super <a rel="nofollow">T</a>> predicate)</code> <p>返回由该流的元素组成的流,该元素与给定的谓词匹配。</p> </td> </tr> </tbody> </table> 在执行过程中,流将元素逐一输送给filter,并筛选出Predicate函数执行结果为true的元素。 public static void main(String[] args) { List<User> list = new ArrayList<>(); initList(list); List<User> userList = list.stream().filter(u -> "男".equals(u.getSex())).collect(Collectors.toList()); userList.forEach(System.out::println); } private static void initList(List<User> list) { list.add(new User(1, "赵云", "男", 18, 178)); list.add(new User(2, "后裔", "男", 20, 188)); list.add(new User(1, "妲己", "女", 17, 175)); } **2、distinct方法:去重** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">distinct</a>()</code> <p>返回一个包含不同的元素流(根据 <a rel="nofollow"><code>Object.equals(Object)</code></a>)这个流。</p> </td> </tr> </tbody> </table> long count = IntStream.of(1, 2, 3, 4, 3, 2, 1).distinct().count(); System.out.println(count); // 4 **3、limit方法:截取多少条数据 ** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">limit</a>(long maxSize)</code> <p>返回一个包含该流的元素流,截断长度不超过 <code>maxSize</code>。</p> </td> </tr> </tbody> </table> **4、skip方法:跳过前几条数据 ** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">skip</a>(long n)</code> <p>返回一个包含此流的其余部分丢弃的流的第一 <code>n</code>元素后流。</p> </td> </tr> </tbody> </table> List<User> userList = list.stream().filter(u -> "男".equals(u.getSex())).skip(1).collect(Collectors.toList()); userList.forEach(System.out::println); // 后裔 **5、map方法:映射,将一个元素转换成另一个元素类型** * * <table> <tbody> <tr> <td><code><R> <a rel="nofollow">Stream</a><R></code></td> <td><code><a rel="nofollow">map</a>(<a rel="nofollow">Function</a><? super <a rel="nofollow">T</a>,? extends R> mapper)</code> <p>返回一个流,包括将给定函数应用到该流元素的结果。</p> </td> </tr> </tbody> </table> 对流中的每个元素执行一个函数,使得元素转换成另一种类型输出。流会将每一个元素输送给map函数,并执行map中的Lambda表达式,最后将执行结果存入一个新的流中。 List<String> nameList = list.stream().map(User::getName).collect(Collectors.toList()); nameList.forEach(System.out::println); **6、flatMap方法:合并多个流** * * <table> <tbody> <tr> <td><code><R> <a rel="nofollow">Stream</a><R></code></td> <td><code><a rel="nofollow">flatMap</a>(<a rel="nofollow">Function</a><? super <a rel="nofollow">T</a>,? extends <a rel="nofollow">Stream</a><? extends R>> mapper)</code> <p>返回由将所提供的映射函数应用到每个元素的映射流的内容替换此流的每个元素的结果的结果流。</p> </td> </tr> </tbody> </table> 注意: * map 方法:是将元素转换成另一种类型输出,返回一个大流,大流里面包含了一个个小流, * flatMap 方法:是将大流中的这些小流合并成一个流返回。 List<String> list = new ArrayList<String>(); list.add("a,b,c,d"); list.add("e,f,a,b"); list.add("h,i,c,d"); List<String[]> list1 = list.stream().map(line -> line.split(",")).collect(Collectors.toList()); List<String> list21 = list.stream().map(line -> line.split(",")).flatMap((arr) -> Arrays.stream(arr)).collect(Collectors.toList()); List<String> list22 = list.stream().map(line -> line.split(",")).flatMap(Arrays::stream).distinct().collect(Collectors.toList()); ![20200901170258875.png][] **7、peek方法:遍历操作** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">peek</a>(<a rel="nofollow">Consumer</a><? super <a rel="nofollow">T</a>> action)</code> <p>返回由该流的元素组成的流,并在所提供的流中执行所提供的每个元素上的动作。</p> </td> </tr> </tbody> </table> peek()操作,当元素被消费时(终端操作方法被调用时),会触发前面peek。有多少个就触发多少次。 // 不会有任何的输出 Stream.of("a", "b", "c", "d").peek(e -> System.out.print(e + " ")); // 会输出 Stream.of("a", "b", "c", "d").peek(System.out::print).collect(Collectors.toSet()); **8、unordered方法:返回一个无序的流,对于不关心顺序的数据处理和并行配合使用更佳。 ** * * <table> <tbody> <tr> <td><code><a rel="nofollow">S</a></code></td> <td><code><a rel="nofollow">unordered</a>()</code> <p>返回一个等效流, <a rel="nofollow">unordered</a>。</p> </td> </tr> </tbody> </table> IntStream.rangeClosed(0,9).unordered().forEach(System.out::print); // 0123456789 System.out.println(); IntStream.rangeClosed(0,9).parallel().unordered().forEach(System.out::print); // 6570918342 如果不关心顺序,则可以使用并行,这样使用,能提高CPU的利用率,进而提高处理的效率。比如。某活动(参与用户大数量)随机获取三名幸运观众。 **9、sorted方法:对数据排序** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">sorted</a>()</code> <p>返回由该流的元素组成的流,按自然顺序排序。</p> </td> </tr> <tr> <td><code><a rel="nofollow">Stream</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">sorted</a>(<a rel="nofollow">Comparator</a><? super <a rel="nofollow">T</a>> comparator)</code> <p>返回一个包含该流的元素流,根据提供的 <code>Comparator</code>排序。</p> </td> </tr> </tbody> </table> 应用非常广泛,重点掌握。 // 年龄升序 List<User> userList = list.stream().sorted(Comparator.comparingInt(User::getAge)).collect(Collectors.toList()); userList.forEach(System.out::println); ### 四、终端操作 ### **1、allMatch方法:匹配所有元素都满足返回true** **2、anyMatch方法:匹配任何元素有满足返回true** **3、noneMatch方法:匹配所有元素都不满足返回true** noneMatch与allMatch恰恰相反 <table> <tbody> <tr> <td><code>boolean</code></td> <td><code><a rel="nofollow">allMatch</a>(<a rel="nofollow">Predicate</a><? super <a rel="nofollow">T</a>> predicate)</code> <p>返回此流中的所有元素是否匹配所提供的谓词。</p> </td> </tr> <tr> <td><code>boolean</code></td> <td><code><a rel="nofollow">anyMatch</a>(<a rel="nofollow">Predicate</a><? super <a rel="nofollow">T</a>> predicate)</code> <p>返回此流中的任何元素是否匹配所提供的谓词。</p> </td> </tr> <tr> <td><code>boolean</code></td> <td><code><a rel="nofollow">noneMatch</a>(<a rel="nofollow">Predicate</a><? super <a rel="nofollow">T</a>> predicate)</code> <p>返回此流中的任何元素是否匹配所提供的谓词。</p> </td> </tr> </tbody> </table> public static void main(String[] args) { List<User> list = new ArrayList<>(); initList(list); // 判断所有人是否都为男性 boolean b1 = list.stream().allMatch(user -> "男".equals(user.getSex())); System.out.println(b1); // false // 判断所有人中是否有个男性 boolean b2 = list.stream().anyMatch(user -> "男".equals(user.getSex())); System.out.println(b2); // true // 判断所有人中是否都为女性 boolean b3 = list.stream().noneMatch(user -> "男".equals(user.getSex())); System.out.println(b3); // false } private static void initList(List<User> list) { list.add(new User(1, "赵云", "男", 18, 178)); list.add(new User(2, "后裔", "男", 20, 188)); list.add(new User(1, "妲己", "女", 17, 175)); } **4、findAny方法:获取任一元素。** findAny能够从流中随便选一个元素出来(该操作行为明确不确定的),它返回一个Optional类型的元素。比如:随机抽取一名幸运观众。 **5、findFirst方法:获取第一个元素** * * <table> <tbody> <tr> <td><code><a rel="nofollow">Optional</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">findAny</a>()</code> <p>返回一个 <a rel="nofollow"><code>Optional</code></a>描述一些流元素,或一个空的 <code>Optional</code>如果流是空的。</p> </td> </tr> <tr> <td><code><a rel="nofollow">Optional</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">findFirst</a>()</code> <p>返回一个 <a rel="nofollow"><code>Optional</code></a>描述此流的第一个元素,或者一个空的 <code>Optional</code>如果流是空的。</p> </td> </tr> </tbody> </table> OptionalInt optional = IntStream.rangeClosed(1, 1000).findFirst(); System.out.println(optional.getAsInt()); OptionalInt optionalInt = IntStream.rangeClosed(1, 1000).parallel().findAny(); System.out.println(optionalInt.getAsInt()); **6、forEach和forEachOrdered方法:遍历每一个元素** * * <table> <tbody> <tr> <td><code>void</code></td> <td><code><a rel="nofollow">forEach</a>(<a rel="nofollow">Consumer</a><? super <a rel="nofollow">T</a>> action)</code> <p>对该流的每个元素执行一个动作。</p> </td> </tr> <tr> <td><code>void</code></td> <td><code><a rel="nofollow">forEachOrdered</a>(<a rel="nofollow">Consumer</a><? super <a rel="nofollow">T</a>> action)</code> <p>对该流的每个元素执行一个操作,如果流有一个定义的遇到顺序,则在该流的遇到顺序中执行一个动作。</p> </td> </tr> </tbody> </table> IntStream.rangeClosed(0,9).parallel().unordered().forEachOrdered(System.out::print); // 0123456789 **7、mapToXxx方法:将普通流转换成数值流** 提供了将普通流转换成数值流的三种方法:mapToInt、mapToDouble、mapToLong。 * * <table> <tbody> <tr> <td><code><a rel="nofollow">DoubleStream</a></code></td> <td><code><a rel="nofollow">mapToDouble</a>(<a rel="nofollow">ToDoubleFunction</a><? super <a rel="nofollow">T</a>> mapper)</code> <p>返回一个包含应用给定的功能,该流的元素的结果 <code>DoubleStream</code>。</p> </td> </tr> <tr> <td><code><a rel="nofollow">IntStream</a></code></td> <td><code><a rel="nofollow">mapToInt</a>(<a rel="nofollow">ToIntFunction</a><? super <a rel="nofollow">T</a>> mapper)</code> <p>返回一个包含应用给定的功能,该流的元素的结果 <code>IntStream</code>。</p> </td> </tr> <tr> <td><code><a rel="nofollow">LongStream</a></code></td> <td><code><a rel="nofollow">mapToLong</a>(<a rel="nofollow">ToLongFunction</a><? super <a rel="nofollow">T</a>> mapper)</code> <p>返回一个包含应用给定的功能,该流的元素的结果 <code>LongStream</code>。</p> </td> </tr> </tbody> </table> IntStream intStream = list.stream().mapToInt(User::getAge); **8、数值计算** 每种数值流都提供了数值计算函数,如max、min、sum等。 * * <table> <tbody> <tr> <td><code><a rel="nofollow">Optional</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">max</a>(<a rel="nofollow">Comparator</a><? super <a rel="nofollow">T</a>> comparator)</code> <p>返回最大元本流根据提供的 <code>Comparator</code>。</p> </td> </tr> <tr> <td><code><a rel="nofollow">Optional</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">min</a>(<a rel="nofollow">Comparator</a><? super <a rel="nofollow">T</a>> comparator)</code> <p>返回最小元本流根据提供的 <code>Comparator</code>。</p> </td> </tr> </tbody> </table> OptionalDouble average = list.stream().mapToInt(User::getAge).average(); System.out.println(average.getAsDouble()); // 18.333333333333332 **9、reduce方法:能实现归约,输出一个值(任何类型)。** **归约**是将集合中的所有元素经过指定运算,折叠成一个元素值输出。比如:求最值、平均数等。 * * <table> <tbody> <tr> <td><code><a rel="nofollow">Optional</a><<a rel="nofollow">T</a>></code></td> <td><code><a rel="nofollow">reduce</a>(<a rel="nofollow">BinaryOperator</a><<a rel="nofollow">T</a>> accumulator)</code> <p>对这一 <a rel="nofollow">reduction</a>流元素,使用 <a rel="nofollow">associative</a>累积函数,并返回一个 <code>Optional</code>描述价值减少,如果任何。</p> </td> </tr> <tr> <td><code><a rel="nofollow">T</a></code></td> <td><code><a rel="nofollow">reduce</a>(<a rel="nofollow">T</a> identity, <a rel="nofollow">BinaryOperator</a><<a rel="nofollow">T</a>> accumulator)</code> <p>对这一 <a rel="nofollow">reduction</a>流元素,使用提供的价值认同和 <a rel="nofollow">associative</a>累积函数,返回值减少。</p> </td> </tr> <tr> <td><code><U> U</code></td> <td><code><a rel="nofollow">reduce</a>(U identity, <a rel="nofollow">BiFunction</a><U,? super <a rel="nofollow">T</a>,U> accumulator, <a rel="nofollow">BinaryOperator</a><U> combiner)</code> <p>对这一 <a rel="nofollow">reduction</a>流元素,使用提供的身份,积累和组合功能。</p> </td> </tr> </tbody> </table> 三个参数的含义: > **identity:**一个初始化的值;这个初始化的值其类型是泛型U(与Stream中元素的类型可以不一样也可以一样),与Reduce方法返回的类型一致; > > **accumulator:**其类型是 **BiFunction函数**(输入U与T两个类型的参数数据,而返回计算后的数据) > > **combiner:** 其类型是 **BinaryOperator函数**,支持的是对U类型的对象进行操作,主要是使用在并行计算的场景下。注意:如果Stream是非并行时,第三个参数不起作用。 ArrayList<String> result = Stream.of("a", "b", "c", "d").reduce(new ArrayList<>(), (u, s) -> { u.add(s); return u; }, (strings, strings2) -> strings); //无效的 System.out.println(result); //[a, b, c, d] Integer reduce = Stream.of(1, 2, 3).reduce(4, (integer, integer2) -> integer + integer2, (integer, integer2) -> integer * integer2); //无效的 System.out.println(reduce); //10 Integer reduce2 = Stream.of(1, 2, 3).parallel().reduce(4, (integer, integer2) -> integer + integer2, //开启三个线程,每个线程里对应一个元素+4 (integer, integer2) -> integer * integer2); //有效的,它会将不同线程计算的结果调用combiner做汇总后返回。 System.out.println(reduce2); //210 int accResult = Stream.of(1, 2, 3) .reduce(4, (acc, item) -> { System.out.println("acc : " + acc); acc += item; System.out.println("item: " + item); System.out.println("acc+ : " + acc); System.out.println("--------"); return acc; }); System.out.println("accResult: " + accResult); //10 ![20200902101740197.png][] 也可以进行元素求最值、平均数等操作。 int sum1 = 0; for (User user : list) { sum1 = sum1 + user.getAge(); } System.out.println(sum1); // 55 Optional<Integer> sum = list.stream().map(user -> user.getAge()).reduce((u1, u2) -> u1 + u2); System.out.println(sum.get()); // 55 **10、collect方法:收集方法,对于 reduce 和 collect方法个人更喜欢collect的强大。** * * <table> <tbody> <tr> <td><code><R,A> R</code></td> <td><code><a rel="nofollow">collect</a>(<a rel="nofollow">Collector</a><? super <a rel="nofollow">T</a>,A,R> collector)</code> <p>执行 <a rel="nofollow">mutable reduction</a>操作对元素的使用 <code>Collector</code>流。</p> </td> </tr> <tr> <td><code><R> R</code></td> <td><code><a rel="nofollow">collect</a>(<a rel="nofollow">Supplier</a><R> supplier, <a rel="nofollow">BiConsumer</a><R,? super <a rel="nofollow">T</a>> accumulator, <a rel="nofollow">BiConsumer</a><R,R> combiner)</code> <p>执行该流的元素 <a rel="nofollow">mutable reduction</a>操作。</p> </td> </tr> </tbody> </table> collect(Collector c) 将流转换为其他形式,接收一个 Collector接口的实现,用于给Stream中元素做汇总处理。 **下面汇总的方法来自:[JAVA8之collect总结][JAVA8_collect]** <table> <tbody> <tr> <td>工厂方法</td> <td>返回类型</td> <td>作用</td> </tr> <tr> <td>toList</td> <td>List<T></td> <td>把流中所有元素收集到List中</td> </tr> <tr> <td colspan="3"> 示例:List<Menu> menus = Menu.getMenus.stream().collect(Collectors.toList())</td> </tr> <tr> <td>toSet</td> <td>Set<T></td> <td>把流中所有元素收集到Set中,并删除重复项</td> </tr> <tr> <td colspan="3"> 示例:Set<Menu> menus = Menu.getMenus.stream().collect(Collectors.toSet())</td> </tr> <tr> <td>toCollection</td> <td>Collection<T></td> <td>把流中所有元素收集到创建的集合中</td> </tr> <tr> <td colspan="3"> 示例:ArrayList<Menu> menus = Menu.getMenus.stream().collect(Collectors.toCollection(ArrayList::new))</td> </tr> <tr> <td>Counting</td> <td>Long</td> <td>计算流中元素的个数</td> </tr> <tr> <td colspan="3"> 示例:Long count = Menu.getMenus.stream().collect(counting);</td> </tr> <tr> <td>SummingInt</td> <td>Integer</td> <td>对流中元素的一个整数属性求和</td> </tr> <tr> <td colspan="3"> 示例:Integer count = Menu.getMenus.stream().collect(summingInt(Menu::getCalories))</td> </tr> <tr> <td>averagingInt</td> <td>Double</td> <td>计算流中元素Integer属性的平均值</td> </tr> <tr> <td colspan="3"> 示例:Double averaging = Menu.getMenus.stream().collect(averagingInt(Menu::getCalories))</td> </tr> <tr> <td>summarizingInt</td> <td>IntSummaryStatistics</td> <td>收集流中元素Integer属性的统计值,如:平均值</td> </tr> <tr> <td colspan="3"> 示例:IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(Menu::getCalories));</td> </tr> <tr> <td>Joining</td> <td>String</td> <td>连接流中每个元素的字符串</td> </tr> <tr> <td colspan="3"> 示例:String name = Menu.getMenus.stream().map(Menu::getName).collect(joining(“, ”))</td> </tr> <tr> <td>maxBy</td> <td>Optional<T></td> <td>根据比较器选出的最大值的optional<br> 如果为空返回的是Optional.empty()</td> </tr> <tr> <td colspan="3"> 示例:Optional<Menu> fattest = Menu.getMenus.stream().collect(maxBy(Menu::getCalories))</td> </tr> <tr> <td>minBy</td> <td>Optional<T></td> <td>根据比较器选出的最小值的的optional<br> 如果为空返回的是Optional.empty()</td> </tr> <tr> <td colspan="3"> 示例: Optional<Menu> lessest = Menu.getMenus.stream().collect(minBy(Menu::getCalories))</td> </tr> <tr> <td>Reducing</td> <td>归约操作产生的类型</td> <td>从一个作为累加器的初始值开始,利用binaryOperator与流中的元素逐个结合,从而将流归约为单个值</td> </tr> <tr> <td colspan="3"> 示例:int count = Menu.getMenus.stream().collect(reducing(0,Menu::getCalories,Integer::sum));</td> </tr> <tr> <td>collectingAndThen</td> <td>转换函数返回的类型</td> <td>包裹另一个转换器,对其结果应用转换函数</td> </tr> <tr> <td colspan="3"> 示例:Int count = Menu.getMenus.stream().collect(collectingAndThen(toList(),List::size))</td> </tr> <tr> <td>groupingBy</td> <td>Map<K,List<T>></td> <td>根据流中元素的某个值对流中的元素进行分组,属性值为K, 结果为V</td> </tr> <tr> <td colspan="3"> 示例:Map<Type,List<Menu>> menuType=Menu.getMenus.stream().collect(groupingby(Menu::getType))</td> </tr> <tr> <td>partitioningBy</td> <td>Map<Boolean,List<T>></td> <td>根据流中每个元素应用谓语的结果(true/false)进行分区</td> </tr> <tr> <td colspan="3"> 示例:Map<Boolean,List<Menu>> menuType=Menu.getMenus.stream().collect(partitioningBy(Menu::isType));</td> </tr> </tbody> </table> 这里使用几个方法,具体请查看Collectors API。 **1. toMap方法: ** <table> <tbody> <tr> <td style="width:234px;"><code>static <T,K,U> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Map</a><K,U>></code></td> <td style="width:670px;"><code><a rel="nofollow">toMap</a>(<a rel="nofollow">Function</a><? super T,? extends K> keyMapper, <a rel="nofollow">Function</a><? super T,? extends U> valueMapper)</code> <p>返回一个 <code>Collector</code>积累成一个 <code>Map</code>元素的键和值是应用提供的函数映射到输入元素的结果。</p> </td> </tr> <tr> <td style="width:234px;"><code>static <T,K,U> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Map</a><K,U>></code></td> <td style="width:670px;"><code><a rel="nofollow">toMap</a>(<a rel="nofollow">Function</a><? super T,? extends K> keyMapper, <a rel="nofollow">Function</a><? super T,? extends U> valueMapper, <a rel="nofollow">BinaryOperator</a><U> mergeFunction)</code> <p>返回一个 <code>Collector</code>积累成一个 <code>Map</code>元素的键和值是应用提供的函数映射到输入元素的结果。</p> </td> </tr> <tr> <td style="width:234px;"><code>static <T,K,U,M extends <a rel="nofollow">Map</a><K,U>><br><a rel="nofollow">Collector</a><T,?,M></code></td> <td style="width:670px;"><code><a rel="nofollow">toMap</a>(<a rel="nofollow">Function</a><? super T,? extends K> keyMapper, <a rel="nofollow">Function</a><? super T,? extends U> valueMapper, <a rel="nofollow">BinaryOperator</a><U> mergeFunction, <a rel="nofollow">Supplier</a><M> mapSupplier)</code> <p>返回一个 <code>Collector</code>积累成一个 <code>Map</code>元素的键和值是应用提供的函数映射到输入元素的结果。</p> </td> </tr> </tbody> </table> 如果需求使用线程安全的Map,可以用toConcurrentMap、groupingByConcurrent。 **使用两个参数** 注意: * 当key重复时,会抛出异常:java.lang.IllegalStateException: Duplicate key \* * 当value为 null 时,会抛出异常:java.lang.NullPointerException public static void main(String[] args) { List<User> list = new ArrayList<>(); initList(list); Map<String, Integer> map= list.stream().collect(Collectors.toMap(User::getName, User::getAge)); System.out.println(map); // {妲己=18, 貂蝉=18, 露娜=20, 后裔=20, 赵云=18, 鲁班=9} } private static void initList(List<User> list) { list.add(new User(1, "赵云", "男", 18, 178)); list.add(new User(2, "后裔", "男", 20, 190)); list.add(new User(6, "妲己", "女", 18, 175)); list.add(new User(3, "鲁班", "男", 9, 155)); list.add(new User(8, "貂蝉", "女", 18, 175)); list.add(new User(9, "露娜", "女", 20, 175)); } **使用三个参数** 当两个key相同时,只能有一个key存在,那对应的value如何处理? value交由我们自己处理。 // key重复,v覆盖 Map<String, User> collect = list.stream().collect(Collectors.toMap(User::getSex, user -> user, (oldUser, newUser)->newUser)); System.out.println(collect); // {女=User{id=1, name='露娜', sex='女', age=20, height=175}, 男=User{id=1, name='鲁班', sex='男', age=9, height=155}} **使用四个参数** mergeFunction和mapSupplier 调用者可以自定义希望返回什么类型的Map // List转成Map,且保证List的顺序,使用LinedHashMap。 LinkedHashMap<Integer, User> linkedHashMap = list.stream() .sorted(Comparator.comparingInt(User::getId)) .collect(Collectors.toMap(User::getId, user -> user, (oldUser, newUser) -> newUser, LinkedHashMap::new)); linkedHashMap.forEach((k, v) -> { System.out.println(k + "==" + v); }); ![20200902142810231.png][] **2. mapping 方法:** <table> <tbody> <tr> <td style="width:254px;"><code>static <T,U,A,R> <a rel="nofollow">Collector</a><T,?,R></code></td> <td style="width:650px;"><code><a rel="nofollow">mapping</a>(<a rel="nofollow">Function</a><? super T,? extends U> mapper, <a rel="nofollow">Collector</a><? super U,A,R> downstream)</code> <p>适应 <code>Collector</code>接受型 <code>U</code>元素之一接受元素类型 <code>T</code>应用映射功能,每个输入元素之前的积累。</p> </td> </tr> </tbody> </table> // 和中间操作map方法类似 String collect = list.stream().collect(Collectors.mapping(u -> { return new Float(u.getHeight() / 100 + "." + u.getHeight() % 100).toString(); }, Collectors.joining(","))); System.out.println(collect); // 1.78,1.9,1.75,1.55,1.75,1.75 **3. maxBy、minBy方法:** <table> <tbody> <tr> <td><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Optional</a><T>></code></td> <td><code><a rel="nofollow">maxBy</a>(<a rel="nofollow">Comparator</a><? super T> comparator)</code> <p>返回一个 <code>Collector</code>产生极大元根据给定的 <code>Comparator</code>,描述为一个 <code>Optional<T></code>。</p> </td> </tr> <tr> <td><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Optional</a><T>></code></td> <td><code><a rel="nofollow">minBy</a>(<a rel="nofollow">Comparator</a><? super T> comparator)</code> <p>返回一个 <code>Collector</code>产生最小的元素按照一定的 <code>Comparator</code>,描述为一个 <code>Optional<T></code>。</p> </td> </tr> <tr> <td><code>static <T,A,R,RR> <a rel="nofollow">Collector</a><T,A,RR></code></td> <td><code><a rel="nofollow">collectingAndThen</a>(<a rel="nofollow">Collector</a><T,A,R> downstream, <a rel="nofollow">Function</a><R,RR> finisher)</code> <p>适应 <code>Collector</code>执行一个额外的加工转化。</p> </td> </tr> </tbody> </table> 当使用maxBy、minBy统计最值时,结果会封装在Optional中。 如果我们明确结果不可能为null时,可以采用 collectingAndThen函数包裹maxBy、minBy,从而将Optional对象进行转换值。 // age最大 Optional<User> optional = list.stream().collect(Collectors.maxBy((u1, u2) -> { return u1.getAge() - u2.getAge(); })); System.out.println(optional); // age最小 User user = list.stream().collect(Collectors.collectingAndThen(Collectors.minBy((u1, u2) -> { return u1.getAge() - u2.getAge(); }), Optional::get)); System.out.println(user); ![20200902144937124.png][] **4. groupBy和groupingByConcurrent方法:** API有几个重载的方法,对数据分组,并且可以无限的分组下去。 注意:groupBy返回的Map,groupingByConcurrent 返回的是 ConcurrentMap。 **单字段分组** // 根据性别分组 ConcurrentMap<String, List<User>> concurrentMap = list.stream() .collect(Collectors.groupingByConcurrent(User::getSex)); concurrentMap.forEach((k, v) -> { System.out.println(k + "==" + v); }); ![20200902145756908.png][] **多字段分组** // 根据性别,年龄分组 Map<String, Map<Integer, List<User>>> map = list.stream() .collect(Collectors.groupingBy(User::getSex, Collectors.groupingBy(User::getAge))); map.forEach((k, m) -> { m.forEach((k2, v) -> { System.out.println(k + "==" + k2 + "==" + v); }); }); ![2020090215012078.png][] **单字段分组并统计** // 根据性别分组并统计个数 Map<String, Long> map = list.stream() .collect(Collectors.groupingBy(User::getSex, Collectors.counting())); map.forEach((k, v) -> { System.out.println(k + "==" + v); }); == 女==3 男==3 **5. partitioningBy方法:分区** <table> <tbody> <tr> <td style="width:292px;"><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Map</a><<a rel="nofollow">Boolean</a>,<a rel="nofollow">List</a><T>>></code></td> <td style="width:612px;"><code><a rel="nofollow">partitioningBy</a>(<a rel="nofollow">Predicate</a><? super T> predicate)</code> <p>返回一个 <code>Collector</code>分区根据 <code>Predicate</code>输入元素,并将它们组织成一个 <code>Map<Boolean, List<T>></code>。</p> </td> </tr> <tr> <td style="width:292px;"><code>static <T,D,A> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">Map</a><<a rel="nofollow">Boolean</a>,D>></code></td> <td style="width:612px;"><code><a rel="nofollow">partitioningBy</a>(<a rel="nofollow">Predicate</a><? super T> predicate, <a rel="nofollow">Collector</a><? super T,A,D> downstream)</code> <p>返回一个 <code>Collector</code>分区的输入元素,根据 <code>Predicate</code>,降低值在每个分区根据另一 <code>Collector</code>,并将它们组织成一个 <code>Map<Boolean, D></code>其值是下游减少的结果。</p> </td> </tr> </tbody> </table> 分区是分组的一种特殊情况,它只能分成true、false两组。 List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 5, 5, 6, 9); Map<Boolean, List<Integer>> partition = list.stream().collect(Collectors.partitioningBy(x -> x >= 4)); System.out.println(partition); Map<Boolean, Map<Boolean, List<Integer>>> partition2 = list.stream() .collect(Collectors.partitioningBy(x -> x >= 4, Collectors.partitioningBy(x -> x > 6))); System.out.println(partition2); //也可以结合groupBy等 Map<Boolean, Map<Integer, List<Integer>>> collect = list.stream() .collect(Collectors.partitioningBy(x -> x >= 4, Collectors.groupingBy(x -> x))); System.out.println(collect); ![20200902151240473.png][] **7. 生成统计信息的方法** <table> <tbody> <tr> <td><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">DoubleSummaryStatistics</a>></code></td> <td><code><a rel="nofollow">summarizingDouble</a>(<a rel="nofollow">ToDoubleFunction</a><? super T> mapper)</code> <p>返回一个 <code>Collector</code>采用 <code>double</code>-producing映射功能,每个输入元素,并返回结果值汇总统计。</p> </td> </tr> <tr> <td><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">IntSummaryStatistics</a>></code></td> <td><code><a rel="nofollow">summarizingInt</a>(<a rel="nofollow">ToIntFunction</a><? super T> mapper)</code> <p>返回一个 <code>Collector</code>采用 <code>int</code>-producing映射功能,每个输入元素,并返回结果值汇总统计。</p> </td> </tr> <tr> <td><code>static <T> <a rel="nofollow">Collector</a><T,?,<a rel="nofollow">LongSummaryStatistics</a>></code></td> <td><code><a rel="nofollow">summarizingLong</a>(<a rel="nofollow">ToLongFunction</a><? super T> mapper)</code> <p>返回一个 <code>Collector</code>采用 <code>long</code>-producing映射功能,每个输入元素,并返回结果值汇总统计。</p> </td> </tr> </tbody> </table> IntSummaryStatistics、LongSummaryStatistics和DoubleSummaryStatistics对象包含的很多统计信息。比如:最值,平均值等。 // 统计list的age信息 IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(User::getAge)); System.out.println(summary.getMax()); // 20 System.out.println(summary.getMin()); // 9 System.out.println(summary.getSum()); // 103 System.out.println(summary.getAverage()); // 17.166666666666668 System.out.println(summary.getCount()); // 6 也可以使用 combine方法:将一个IntSummaryStatistics与另一个IntSummaryStatistics组合起来(必须是同一类型)。 * * <table> <tbody> <tr> <td><code>void</code></td> <td><code><a rel="nofollow">accept</a>(int value)</code> <p>将一个新的值记录到汇总信息中</p> </td> </tr> <tr> <td><code>void</code></td> <td><code><a rel="nofollow">combine</a>(<a rel="nofollow">IntSummaryStatistics</a> other)</code> <p>结合另一个 <code>IntSummaryStatistics</code>状态进入这一。</p> </td> </tr> </tbody> </table> // 统计list的age信息 IntSummaryStatistics summary = list.stream().collect(Collectors.summarizingInt(User::getAge)); // 添加一个age IntSummaryStatistics summary2 = new IntSummaryStatistics(); summary2.accept(100); summary.combine(summary2); System.out.println(summary.getMax()); // 100 System.out.println(summary.getMin()); // 9 System.out.println(summary.getSum()); // 203 System.out.println(summary.getAverage()); // 29.0 System.out.println(summary.getCount()); // 7 Stream 结合Lambda表达式,函数式接口等操作,使代码简洁,优雅,对数据的处理更加灵活。在开发中推荐使用。 **参考文章:** [谈谈并行流parallelStream][parallelStream] [Java 8系列之Stream的基本语法详解][Java 8_Stream] —— Stay Hungry. Stay Foolish. 求知若饥,虚心若愚。 [20200901170258875.png]: /images/20221123/6b7a3eef58084918a4a011e6f1959dfc.png [20200902101740197.png]: /images/20221123/bc360f25b47a45128c1e2ace6f95fddf.png [JAVA8_collect]: https://www.cnblogs.com/xiaoshuaidiboke/articles/8036960.html [20200902142810231.png]: /images/20221123/5f8d1b7e877f4cb4b42bd4cd9f5a31bf.png [20200902144937124.png]: /images/20221123/e2044ad396684086ba03aa8f10737f24.png [20200902145756908.png]: /images/20221123/ce19b0c0c5f946ccab4a28ac03c43d57.png [2020090215012078.png]: /images/20221123/a2bfb65b53c74a89a1b0970bc6929118.png [20200902151240473.png]: /images/20221123/846638451b0a474ca612ca611eb081cc.png [parallelStream]: https://blog.csdn.net/qq_33591903/article/details/107990065 [Java 8_Stream]: https://blog.csdn.net/io_field/article/details/54971761
相关 Java 8 Stream API:简化操作的关键 Java 8的Stream API确实提供了一种更加简洁、高效的方式来处理集合数据。以下几点是Stream API的关键特性: 1. **操作链式化**:在传统的集合操作中, 刺骨的言语ヽ痛彻心扉/ 2024年09月15日 12:27/ 0 赞/ 8 阅读
相关 Java 8 Stream API 操作案例解析 Java 8的Stream API提供了一种新的、并行的处理集合元素的方式。以下是一些常见的Stream操作案例: 1. **过滤**:根据某种条件筛选元素。 ```java 墨蓝/ 2024年09月11日 07:51/ 0 赞/ 18 阅读
相关 Java8 Stream API详细操作 一、Stream流的基本概念 1、什么是Stream流? 流是Java8引入的全新概念,它用来处理集合中的数据,对集合(Collection)对象功能的增强,专注于对集 ╰+哭是因爲堅強的太久メ/ 2022年12月02日 13:49/ 0 赞/ 236 阅读
相关 Java8 Stream API Java8 Stream API Stream是啥 创建流 创建一个空的流 朴灿烈づ我的快乐病毒、/ 2022年05月31日 11:59/ 0 赞/ 146 阅读
相关 Java 8-Stream API-流操作 java.util.stream.Stream中的Stream接口定义了许多操作。它们可以分为两大类。 filter、map和limit可以连成一条流水线 col 约定不等于承诺〃/ 2022年05月21日 06:55/ 0 赞/ 205 阅读
相关 Java 8-Stream API 流处理 流是一系列数据项,一次只生产一项。程序可以从输入流中一个一个读取数据项,然后以同样的方式将数据项写入输出流。一个程序的输出流很可能是另一个程序的输入流。 流,简 我不是女神ヾ/ 2022年05月21日 06:54/ 0 赞/ 131 阅读
相关 Java8Stream API Stream (java.util.stream.\)是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。 Bertha 。/ 2022年05月15日 13:55/ 0 赞/ 188 阅读
相关 java8新特性 Stream API 终止操作 Stream的终止操作:终止操作会从流的流水线生成结果,其结果可以是任何不适流的操作,例如:List,Integer,甚至是void 一,查找与匹配 List<Em 左手的ㄟ右手/ 2021年11月29日 11:40/ 0 赞/ 215 阅读
相关 Java 8 stream流 lambda详细操作事例 Java 8 stream流: ![20190804192402143.png][] 直接上代码: 一、新建User类准备数据 import lomb 你的名字/ 2021年11月09日 03:31/ 0 赞/ 211 阅读
还没有评论,来说两句吧...