jdk1.8 List集合Stream流式处理
jdk1.8 List集合Stream流式处理
- 一、介绍(为什么需要流Stream,能解决什么问题?)
- 1.1 什么是 Stream?
- 1.2 常见的创建Stream方法
- 1.3 常见的中间操作
- 1.4 常见的终端操作
- 二、创建流Stream
- 2.1 Collection的.stream()方法
- 2.2 数组创建流
- 2.3 静态工厂方法
- 2.4 Stream.builder
- 2.5 从文件创建流
- 三、中间操作
- 3.1 过滤(Filter)
- 3.2 映射(Map)
- 3.3 映射(flatMap)
- 3.4 排序(Sorted)
- 3.5 distinct(去重,含转Set去重)
- 3.6 skip limit(分页)
- 3.7 peek(循环)
- 3.8 mapToInt、mapToDouble ...
- 四、终端操作
- 4.0 foreach(常用于处理List、Map等数据)
- 4.1 收集(Collect)
- 4.1.1 转换为List
- 4.1.2 转换为Set
- 4.1.3 分组汇总(Grouping)
- 4.1.4 toMap(转成Map)
- 4.1.5 reducing 规约(此处只讨论简单使用,重载方法待研究)
- 4.1.6 计数(Counting)
- 4.1.7 汇总求和(Summing)
- 4.1.8 汇总对象(IntSummaryStatistics)
- 4.1.9 获取单个元素 maxBy、 minBy
- 4.1.10 toCollection()
- 4.1.11 mapping
- 4.1.12 joining
- 4.1.13 partitioningBy
- 4.1.14 collectingAndThen
- 4.2. 自定义收集器
- 4.3 归约(Reduce)
- 4.4 查找元素 findFirst、findAny
- 4.5 匹配 anyMatch、allMatch、noneMatch
- 4.6 count max min
- 五、并行流
- 六、总结
- 6.1 Stream的优点:
- 6.2 Stream的缺点
一、介绍(为什么需要流Stream,能解决什么问题?)
Java 8 引入了一个新的抽象层——Stream API,它允许你以声明性方式处理数据集合(包括数组、集合等)。Stream API 提供了一种高效且易于表达的方式来处理数据集合,包括过滤、排序、映射和归约等操作。这种处理方式极大地提高了代码的可读性和可维护性,同时也提升了处理大量数据的性能。
1.1 什么是 Stream?
Stream(流)是 Java 8 引入的一个关键抽象概念,它代表了一个来自数据源的元素队列并支持聚合操作。和迭代器(Iterator)不同,Stream 不存储元素;它们是源到聚合操作的中间桥梁,其操作的执行是延迟的,即只有在需要结果时才执行。
在日常编程中,会经常进行数据(如List、数组)的处理,在没有stram流时,我们的一般操作方式显得比较臃肿,不够优雅简洁。代码如下(原写法):
List<String> list = new ArrayList<>();Collections.addAll(list,"赵子龙","关云长","黄忠","张良","张翼德");ArrayList<String> list1 = new ArrayList<>();list.forEach(s -> {if(s.startsWith("张")) {list1.add(s);}});list1.forEach(s -> System.out.println(s));
stream流式写法
List<String> list = new ArrayList<>();Collections.addAll(list,"赵子龙","关云长","黄忠","张良","张翼德");list.stream().filter(p -> p.startsWith("张")).forEach(System.out::println);
这个例子也告诉我们Stream流写法更加简洁优雅。
流式操作三部曲如下图所示:
如上图所示,概括的讲,可以将stream流操作分为3种类型
:
- 创建Stream
- Stream中间操作
- 终止Stream(终端操作)
每个Stream管道操作类型都包含若干API方法,先列举下各个API方法的功能介绍。
1.2 常见的创建Stream方法
在Java中,创建Stream的方法多种多样,可以从各种数据源生成Stream。以下是一些常见的创建Stream的方法:
方法类型 | 示例代码 | 描述 |
---|---|---|
集合转Stream | List<String> list = Arrays.asList("a", "b", "c");<br>Stream<String> stream = list.stream(); | 通过Collection接口(如List、Set等)的stream() 方法创建Stream。 |
数组转Stream | String[] array = {"d", "e", "f"};<br>Stream<String> stream = Arrays.stream(array); | 通过Arrays类的stream(T[] array) 静态方法,将数组转换为Stream。 |
Stream类静态方法 | Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5); | 使用Stream类的of(T... values) 静态方法,从一组值中创建Stream。 |
无限流生成 | Stream<Double> randomStream = Stream.generate(Math::random); Stream<Integer> infiniteStream = Stream.iterate(0, i -> i + 1); | 使用Stream类的generate(Supplier<T> s) 和iterate(T seed, UnaryOperator<T> f) 静态方法生成无限流。generate 接受一个供应器(Supplier),每次需要新值时调用其get() 方法;iterate 接受一个初始值和一个函数,每次迭代时将当前值作为参数传递给函数,返回的结果作为下一次迭代的值。 |
空Stream | Stream<String> emptyStream = Stream.empty(); | 使用Stream类的empty() 静态方法创建一个空的Stream。 |
构建器 | Stream.Builder<String> builder = Stream.builder();<br>builder.add("Java");<br>builder.add("Python");<br>Stream<String> stream = builder.build(); | 使用Stream.Builder来构建复杂的Stream。首先创建一个Builder对象,然后调用其add 方法添加元素,最后调用build 方法生成Stream。 |
1.3 常见的中间操作
操作名称 | 操作方法 | 描述 |
---|---|---|
过滤 | filter(Predicate<? super T> predicate) | 通过给定的谓词(predicate)测试元素,保留使谓词返回true的元素 |
映射 | map(Function<? super T, ? extends R> mapper) | 将每个元素映射到其对应的转换结果上,转换结果可以是一个新的类型 |
扁平映射 | flatMap(Function<? super T, ? extends Stream<? extends R>> mapper) | 类似于map,但每个元素都可以被映射成一个Stream,然后所有这些Stream会被合并成一个Stream |
排序 | sorted() / sorted(Comparator<? super T> comparator) | 对流中的元素进行排序,可以选择自然排序或自定义排序器 |
截断 | limit(long maxSize) | 限制流中元素的数量,使其不超过给定的最大值 |
跳过 | skip(long n) | 跳过流中的前n个元素 |
并行流 | .parallel() | 将顺序流转换为并行流,以便并行处理 |
串行流 | .sequential() | 将并行流转换回顺序流,以便顺序处理 |
去重 | distinct() | 返回一个由流中不同元素组成的流(基于元素的equals 和hashCode 方法) |
逐元素替换 | peek(Consumer<? super T> action) | 提供一个消费者函数,该函数会在处理每个元素时执行,但不影响流的内容 |
请注意,尽管peek
操作可以看作是一种中间操作(因为它返回Stream本身),但它主要用于调试目的,并不改变流的内容或结构。
1.4 常见的终端操作
操作名称 | 操作方法 | 描述 |
---|---|---|
匹配操作 | anyMatch(Predicate<? super T> predicate) | 是否存在至少一个元素匹配给定的谓词? |
allMatch(Predicate<? super T> predicate) | 是否所有元素都匹配给定的谓词? | |
noneMatch(Predicate<? super T> predicate) | 是否没有元素匹配给定的谓词? | |
查找操作 | findFirst() | 返回流中的第一个元素(作为Optional),如果流为空,则返回Optional.empty() |
findAny() | 返回流中的任意元素(作为Optional),对于并行流,行为可能不同 | |
归约操作 | reduce(BinaryOperator<T> accumulator) | 通过给定的归约操作(如求和、求积)将流中的所有元素组合起来,结果为Optional |
reduce(T identity, BinaryOperator<T> accumulator) | 类似于上一个,但提供了一个初始值,结果为T类型,而非Optional | |
收集操作 | collect(Collectors.toList()) / collect(Collectors.toSet()) 等 | 将流中的元素收集到一个列表、集合或其他容器中,Collectors 类提供了多种收集器 |
最大值/最小值 | max(Comparator<? super T> comparator) / min(Comparator<? super T> comparator) | 根据给定的比较器找到流中的最大或最小元素,结果为Optional |
数组操作 | toArray(T[] generator) / toArray(IntFunction<T[]> generator) | 将流中的元素收集到一个数组中,需要提供一个数组生成器或类型信息 |
计数操作 | count() | 返回流中的元素数量,结果为long类型 |
遍历操作 | forEach(Consumer<? super T> action) | 对流中的每个元素执行给定的操作,这是一个终端操作,因为它有副作用(即执行操作) |
归纳操作 | summaryStatistics() (针对IntStream, LongStream, DoubleStream) | 返回包含各种统计数据的对象,如计数、平均值、最大值、最小值等 |
请注意,forEach
虽然是一个终端操作,但它主要用于执行副作用(即不返回结果的操作),而不是为了获取结果。同样,reduce
、collect
和max
/min
等操作既可以看作是中间操作(因为它们可以在另一个流的上下文中使用,尽管这在实际中并不常见),也可以看作是终端操作(因为它们会触发流的执行并返回一个结果)。但是,在典型的用法中,我们更倾向于将它们视为终端操作。
二、创建流Stream
2.1 Collection的.stream()方法
public static void main(String[] args) {// 1.list创建流List<String> list = new ArrayList<>();Collections.addAll(list, "赵子龙", "关云长", "黄忠", "张良", "张翼德");// 创建一个Stream流Stream<String> stream = list.stream();// 使用流进行操作stream.filter(p -> p.startsWith("张")).forEach(System.out::println);// 2.map创建流(先转换成keySet或entrySet)Map<Object, Object> map = new HashMap<>();map.put("张三", 1);map.put("李四", 8);map.put("王五", 5);// 2.1 keySetmap.keySet().stream().forEach(System.out::println);map.keySet().stream().forEach(p -> System.out.println(p + "= " + map.get(p)));// 2.2 entrySetmap.entrySet().stream().forEach(System.out::println);}
示例中,用list.stream()创建流,用map装换成keySet或entrySet后用.stream()创建流。
2.2 数组创建流
int[] array = {5,6,8,7,5,6,1,0};// 1.从整数数组创建IntStreamIntStream intStream = Arrays.stream(array);String[] strArray = {"东邪","西毒","南帝","北丐","中神通"};// 2.从字符串数组创建StreamStream<String> stringStream = Arrays.stream(strArray);
示例中,用Arrays.stream()创建流(IntStream 、Stream),为开发中最常用创建流的方式。
2.3 静态工厂方法
Stream.of(T… values):通过将一个可变参数的元素列表传递给Stream.of方法来创建一个包含这些元素的Stream流。这对于创建具有少量元素的流非常方便。
Stream.empty():使用Stream.empty()方法创建一个空的Stream流。
Stream.generate(Supplier s):通过提供一个Supplier函数来创建一个无限大小的Stream流,该函数会生成元素。通常,需要使用limit操作限制生成的元素数量。
Stream.iterate(T seed, UnaryOperator f):通过提供初始值(seed)和一个一元操作函数(UnaryOperator)来创建一个包含无限序列的Stream流。例如,可以使用Stream.iterate(0, n -> n + 1)来创建一个自然数序列的Stream流。
- Stream.of(T… values)创建流
// 1.String数组String[] strArr = {"东邪","西毒","南帝","北丐","中神通"};Stream<String> strArrStream = Stream.of(strArr);strArrStream.forEach(System.out::println);// 2.注意:int类型数组,会将数组仅当作一个处理,如下图所示打印的是一个int[]的地址int[] intArr = {5,6,8,7,5,6,1,0};Stream<int[]> intArrStram = Stream.of(intArr);intArrStram.forEach(System.out::println);
打印如下:
- Stream.generate(Supplier s)创建流
// generate创建streamStream<Integer> randomIntStream = Stream.generate(() -> new Random().nextInt(100));randomIntStream.limit(10).forEach(System.out::println);
2.4 Stream.builder
- Stream.builder创建Stream<Integer>
// 1.逐个添加1到10的整数Stream.Builder<Integer> builder = Stream.builder();for (int i = 0; i < 10; i++) {builder.accept(i);}Stream<Integer> builderIntStream = builder.build();
- Stream.builder创建Stream<Long>
// 2.斐波那契数列的前10个数字Stream.Builder<Long> builder1 = Stream.builder();long a = 0; long b = 1;int count = 10;for (int i = 0; i < count; i++) {builder1.accept(a);long next = a + b;a = b;b = next;}Stream<Long> longStream = builder1.build();
2.5 从文件创建流
- 使用Files.lines方法创建文本文件的流
// 逐行读取,创建StreamString path = "names.txt";try {Stream<String> lines = Files.lines(Paths.get(path));List<String> names = lines.collect(Collectors.toList());} catch (IOException e) {throw new RuntimeException(e);}
三、中间操作
基础代码创建list
List<User> userList = new ArrayList<>();User user1 = User.builder().name("曹操").age(12).desc("许昌").build();User user2 = User.builder().name("刘备").age(22).desc("西蜀").build();User user3 = User.builder().name("曹操").age(42).desc("许昌").build();User user4 = User.builder().name(null).age(32).desc("许昌").build();userList.add(user1);userList.add(user2);userList.add(user3);userList.add(user4);
3.1 过滤(Filter)
filter(Predicate<? super T> predicate)
方法接收一个谓词(即返回布尔值的函数),并返回一个包含所有匹配该谓词的元素的流。如下所示,常规过滤可直接简单写,复杂的过滤,可抽取方法,使代码更加清晰。
{...// 1.常规简单过滤List<User> overNList1 = userList.stream().filter(p -> p.getAge() > 18).collect(Collectors.toList());// 2.复杂过滤时,可抽取方法List<User> overNList2 = userList.stream().filter(p -> isOverN(p)).collect(Collectors.toList());...
}// 示例:isOverNprivate static boolean isOverN(User p) {return p.getAge() > 18;}
3.2 映射(Map)
map(Function<? super T, ? extends R> mapper)
方法将流中的每个元素映射到另一个对象,并返回一个新的流。
// 1.装换成简单数据集合,如StringList<String> names = userList.stream().map(User::getName).collect(Collectors.toList());// 2.装换成其它对象集合List<Address> addressList = userList.stream().map(p -> Address.builder().content(p.getDesc()).build()).collect(Collectors.toList());// 3.mapToInt maxToDouble maxToLong等,此类转换好处是可以获取如max、min、average、count等统计类数据long count = userList.stream().mapToInt(User::getAge).count();System.out.println("count = " + count);
3.3 映射(flatMap)
flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)
flatMap操作与map类似,但它被设计用来处理那些将每个输入元素映射到多个输出元素的场景。flatMap首先应用一个函数来映射每个元素,这个函数返回一个流(而不是单个元素)。然后,flatMap将这些流“扁平化”成一个流。
List<String> sentences = Arrays.asList("apple,banana", "cherry,date");
List<String> words = sentences.stream() .flatMap(sentence -> Arrays.stream(sentence.split(","))) .collect(Collectors.toList());
// words: ["apple", "banana", "cherry", "date"]
适用于元素逐个拆分,并组成新的集合。每个元素处理并返回一个新的Stream,然后将多个Stream展开合并为了一个完整的新的Stream。
3.4 排序(Sorted)
sorted()
和 sorted(Comparator<? super T> comparator)
方法对流中的元素进行排序,关于sorted的用法非常多。
// 0.默认规则排序 适用于基本类型List<String> users = Arrays.stream(new String[]{"东邪", "西毒", "南帝", "北丐", "中神通"}).sorted().collect(Collectors.toList());// 1.自定义排序规则List<User> users2 = userList.stream().sorted((o1, o2) -> o2.getAge().compareTo(o1.getAge())).collect(Collectors.toList());// 2.年龄排序 倒叙List<User> ageSortedList = userList.stream().sorted(Comparator.comparing(User::getAge).reversed()).collect(Collectors.toList());// 3.姓名排序 (忽略大小写)List<User> nameSortedList = userList.stream().sorted(Comparator.comparing(User::getName, String.CASE_INSENSITIVE_ORDER)).collect(Collectors.toList());// 4.先按年龄排序,如果年龄相同则按姓名排序List<User> ageAndNameSortedList = userList.stream().sorted(Comparator.comparing(User::getAge).thenComparing(User::getName, String.CASE_INSENSITIVE_ORDER)).collect(Collectors.toList());// 5.排序处理null值 nullsLast或nullsFirst// 6.null排在最前面,其他非null对象根据age倒叙排列 Comparator.reverseOrder()List<User> nullLastSortedList = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.reverseOrder()))).collect(Collectors.toList());nullLastSortedList.forEach(System.out::println);// 7.null排在最前面,其他非null对象根据age正序排列 Comparator.naturalOrder()List<User> nullLastSortedList1 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.naturalOrder()))).collect(Collectors.toList());nullLastSortedList1.forEach(System.out::println);// 8.null排在最前面,其他非null对象根据age正序排列,并将最终结果反转List<User> nullLastSortedList2 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.naturalOrder())).reversed()).collect(Collectors.toList());nullLastSortedList2.forEach(System.out::println);System.out.println("++++++++++++++++++++++++++++++++");// 9.先根据age排序,null排在最前面,其他的倒序排列; null相同的按照level正序排列 level中为null的排前面; 其他的正序排列List<User> nullLastSortedList3 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Comparator.reverseOrder())).thenComparing(Comparator.comparing(User::getLevel, Comparator.nullsFirst(Comparator.naturalOrder())).reversed())).collect(Collectors.toList());nullLastSortedList3.forEach(System.out::println);System.out.println("///");// 10.先根据age排序,null排在最前面,其他的正序排列;List<User> nullLastSortedList4 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Integer::compareTo))).collect(Collectors.toList());nullLastSortedList4.forEach(System.out::println);// 11.先根据age排序,null排在最前面,其他的正序排列,并将最终结果反转List<User> nullLastSortedList6 = userList.stream().sorted(Comparator.comparing(User::getAge, Comparator.nullsFirst(Integer::compareTo).reversed())).collect(Collectors.toList());nullLastSortedList6.forEach(System.out::println);// 12.自定义复杂排序规则userList.stream().sorted(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {Date o1OpTime = DateUtil.parse(o1.getOpTime(), DateUtil.FULL_DAY_FORMAT);Date o2OpTime = DateUtil.parse(o2.getOpTime(), DateUtil.FULL_DAY_FORMAT);// 正序
// return o1OpTime.compareTo(o2OpTime);// 倒叙return o2OpTime.compareTo(o1OpTime);}});
其中,nullsLast同理。
3.5 distinct(去重,含转Set去重)
distinct()
为基础数据简单去重操作, 后文会讲到通过 Collectors.toCollection
结合TreeSet
排序去重
// 1.基本类型简单去重List<String> list = Arrays.asList(new String[]{"刘备", "张飞", "赵云", "刘备"});List<String> collect = list.stream().distinct().collect(Collectors.toList());collect.forEach(System.out::println);// 终端操作去重// 2.toCollection()去重:根据对象某个字段去重(去重规则:取第一条数据,可排序后再去重,如下所示:取年龄最小的对象)TreeSet<User> nameSet = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))));// 3.toCollection()去重:自定义规则去重(同样可先排序再去重)TreeSet<User> nameSet2 = userList.stream().sorted(Comparator.comparing(User::getAge)).collect(Collectors.toCollection(() -> new TreeSet<User>(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return (o1.getName() + o1.getLevel()).equals(o2.getName() + o2.getLevel()) ? 0 : o1.getName().equals(null) ? 1 : -1 ;}})));nameSet.stream().forEach(System.out::println);
3.6 skip limit(分页)
skip(long n)
和limit(long maxSize)
搭配实现分页
int pageSize = 3;int pageNo = 2;// 实现分页功能List<User> users = userList.stream().skip(pageSize * (pageNo - 1)).limit(pageSize).collect(Collectors.toList());users.stream().forEach(System.out::println);
3.7 peek(循环)
peek(Consumer<? super T> action)
实现中间操作的循环操作
// peek为中间操作,不对元素进行操作,可用于记录日志、或调试,与foreach的区别是:peek为中间操作(须有终端操作才会执行Consumer)List<User> users3 = userList.stream().peek(p -> Syste
## 3.8 takeWhilem.out.println(p.getName())).collect(Collectors.toList());
3.8 mapToInt、mapToDouble …
// mapToInt maxToDouble maxToLong等,此类转换好处是可以获取如max、min、average、count等统计类数据long count = userList.stream().mapToInt(User::getAge).count();System.out.println("count = " + count);
四、终端操作
4.0 foreach(常用于处理List、Map等数据)
// 循环逐个元素userList.stream().forEach(System.out::println);
4.1 收集(Collect)
collect
操作接受一个Collector
实例,该实例封装了归约操作的性质,比如如何添加元素、如何合并两个归约结果以及如何完成归约过程等。不过,在实际使用中,我们很少直接创建Collector
实例,而是使用Collectors
工具类提供的静态方法,这些方法返回了预定义的Collector
实例。
以下是一些使用collect
操作的常见示例:
4.1.1 转换为List
将流中的元素收集到一个列表中,可以使用Collectors.toList()
。
// 1.不修改元素类型,可做完若干中间操作后,汇聚成listList<User> users = userList.stream().collect(Collectors.toList());// 2.转换成其他类型List<String> names = userList.stream().map(User::getName).collect(Collectors.toList());
4.1.2 转换为Set
如果你想要去除流中的重复元素并将结果收集到一个集合中,可以使用Collectors.toSet()
。
// 1.转换成Set, 仅基本类型可使用Set<String> set = Arrays.asList(new String[]{"刘备", "张飞", "赵云", "刘备"}).stream().collect(Collectors.toSet());set.stream().forEach(System.out::println);
4.1.3 分组汇总(Grouping)
使用Collectors.groupingBy
可以将流中的元素根据某些属性进行分组。
// 分组 单个字段可简写,多字段组合如下所示Map<String, List<User>> groupList1 = userList.stream().collect(Collectors.groupingBy(User::getName));Map<String, List<User>> groupList2 = userList.stream().collect(Collectors.groupingBy(p -> p.getName() + p.getLevel()));
4.1.4 toMap(转成Map)
默认规定:允许key为null,不允许value为null, 推荐使用3.1写法
// 1.key不重复且value不为null的情况下,可用如下简单写法(为了保险起见,不推荐这种写法)Map<String, User> collect1 = userList.stream().collect(Collectors.toMap(User::getName, p -> p));// 2.处理key重复问题,重复时,取最新的valueMap<String, Integer> collect2= userList.stream().collect(Collectors.toMap(User::getName, User::getLevel, (oldValue, newValue) -> newValue));// 3.处理value为null问题// 3.1 方案一:Optional设置orElse值,如0,空字符串等 (***推荐***),这种写法避免了key重复,也可以避免value为null的情况Map<String, Integer> map3 = userList.stream().collect(Collectors.toMap(User::getName, p -> Optional.ofNullable(p.getLevel()).orElse(0), (oldV, newV) -> newV));// 3.2 方案二:规约转换,null值取最新的Map<String, Integer> collect = userList.stream().collect(HashMap::new, (resultMap, item) -> resultMap.put(item.getName(), item.getLevel()), HashMap::putAll);collect.entrySet().forEach(System.out::println);
4.1.5 reducing 规约(此处只讨论简单使用,重载方法待研究)
// 1.不指定初始值返回OptionalOptional<Integer> reduceSum = userList.stream().map(User::getAge).collect(Collectors.reducing(Integer::sum));// 2.指定初始值,返回intInteger reduceDefaultSum = userList.stream().map(User::getAge).collect(Collectors.reducing(0, Integer::sum));// 3.也可规约汇总对象User reducingLevel = userList.stream().collect(Collectors.reducing(new User(), (o1, o2) -> User.builder().age(o1.getAge() + o2.getAge()).level(o1.getLevel() + o2.getLevel()).build()));
4.1.6 计数(Counting)
Collectors.counting()
可以返回一个收集器,它计算流中的元素数量。
// 计数,与list.size()同效int size = userList.size();Long count = userList.stream().collect(Collectors.counting());
4.1.7 汇总求和(Summing)
// 获取单个字段的和summingIntInteger ageSum = userList.stream().collect(Collectors.summingInt(User::getAge));
4.1.8 汇总对象(IntSummaryStatistics)
如果你有一个包含数值的流,并想要计算这些数值的总和,平均值,最大值,最小值等,可以使用Collectors.summingInt()
(对于整数)等方法。
// 统计对象 IntSummaryStatistics,可根据此对象获取max、min...IntSummaryStatistics intSummaryStatistics = userList.stream().collect(Collectors.summarizingInt(User::getAge));long sum1 = intSummaryStatistics.getSum();long count1 = intSummaryStatistics.getCount();int max = intSummaryStatistics.getMax();int min = intSummaryStatistics.getMin();double average = intSummaryStatistics.getAverage();
4.1.9 获取单个元素 maxBy、 minBy
// 根据某个字段取最小值(最大值等)对应的元素 maxBy minBy,返回Optional对象Optional<User> minUserOptinal = userList.stream().collect(Collectors.minBy(Comparator.comparing(User::getAge)));User user = minUserOptinal.orElse(null);User maxUser = userList.stream().collect(Collectors.maxBy(Comparator.comparing(User::getAge))).orElse(new User());
4.1.10 toCollection()
.toCollection()
是一个收集器(Collector),它用于将流中的元素收集到一个指定的Collection类型中。这个收集器接受一个Supplier作为参数,其中C是Collection的一个子类型,这个Supplier用于提供一个新的、空的Collection实例,流中的元素将被添加到这个实例中。
// toCollection 常与TreeSet去重使用// 1.简单转SetTreeSet<User> userTreeSet = userList.stream().collect(Collectors.toCollection(TreeSet::new));// 2.基本类型转SetTreeSet<String> nameTreeSet = userList.stream().map(User::getName).collect(Collectors.toCollection(TreeSet::new));// 3.自定义不可重复(去重)规则TreeSet<User> userDefineSet = userList.stream().collect(Collectors.toCollection(() -> new TreeSet<>(new Comparator<User>() {@Overridepublic int compare(User o1, User o2) {return o1.getName().compareTo(o2.getName());}})));
4.1.11 mapping
- 简单使用mapping
// 1.与.map等效List<String> names1 = userList.stream().map(User::getName).collect(Collectors.toList());List<String> names2 = userList.stream().collect(Collectors.mapping(User::getName, Collectors.toList()));
- 分组后转换list元素的类型,复杂对象的属性转换和收集
// 2.与groupBy搭配使用,分组后,变换元素类型 User -> AddressMap<String, List<Address>> collect = userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.mapping(p -> {Address address = new Address();address.setContent(p.getDesc());return address;}, Collectors.toList())));// 与groupBy和join结合使用 拼接字符串Map<String, String> map1 =userList.stream().collect(Collectors.groupingBy(User::getName, Collectors.mapping(User::getDesc, Collectors.joining(",", "[", "]"))));map1.entrySet().stream().forEach(p -> {System.out.println(p.getKey() + ":" + p.getValue());});
4.1.12 joining
// 常用于字符串拼接String str = list.stream().collect(Collectors.joining(",", "[", "]"));System.out.println("str = " + str);
4.1.13 partitioningBy
// partitioningBy 根据Predicate<? super T> predicate,汇聚成含有两个元素的map,// 其中一个key为Boolean.TRUE,另一个为Boolean.FALSE,value为满足条件的集合Map<Boolean, List<User>> collect1 = userList.stream().collect(Collectors.partitioningBy(p -> {return p.getAge() >= 18;}, Collectors.toList()));
4.1.14 collectingAndThen
该方法的作用可以简单地概括为“先收集,再做一些操作”。
// 1.常用于去重后TreeSet转成List// 解析 通过Collectors.toCollection收集TreeSet排序去重,然后通过collectingAndThen收集转成ListList<User> userList5 = userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), ArrayList::new));// 最终结果可以理解为如下操作TreeSet<User> treeSet = userList.stream().collect(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))));List<User> users = new ArrayList<>(treeSet);// 2.收集成TreeSet<User>后,转成List<Address>, 详细写法List<Address> addressList = userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), p -> {List<Address> list1 = new ArrayList<>();p.stream().forEach(c -> {Address build = Address.builder().content(c.getDesc()).build();list1.add(build);});return list1;}));//3.简化流式写法如下userList.stream().collect(Collectors.collectingAndThen(Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(User::getName))), p -> p.stream().collect(Collectors.mapping(c -> Address.builder().content(c.getDesc()).build(), Collectors.toList()))));
4.2. 自定义收集器
你还可以使用Collectors.collectingAndThen()
或Collector.of()
来创建自定义的收集器。
List<String> namesList = names.stream().collect(Collectors.collectingAndThen(Collectors.toList(),Collections::unmodifiableList));
上面的代码示例将流中的元素收集到一个列表中,并立即将这个列表转换为不可修改的列表。
4.3 归约(Reduce)
reduce(BinaryOperator<T> accumulator)
方法通过重复结合流中的元素,将它们归约成一个值。
// 1.无初始值 注意orElse为Optional的方法(不推荐)Integer sumOrElse = userList.stream().map(User::getAge).reduce(Integer::sum).orElse(0);// 2.有初始值这种形式的 reduce 接受一个初始值和一个 BiFunction<T, T, T> (推荐)Integer sum = userList.stream().map(User::getAge).reduce(0, Integer::sum);// 3.对象合并,可累加、累乘等User reduce = userList.stream().reduce(new User(), (o1, o2) -> {return User.builder().age(o1.getAge() + o1.getAge()).level(o1.getLevel() & o2.getLevel()).money(o1.getMoney() + o2.getMoney()).build();});
推荐使用有初始值的 reduce,因为它可以优雅地处理空流的情况,并允许你指定一个明确的初始值(可不为0)。
reduce 操作非常灵活,不仅可以用于求和、求积等简单单数据的聚合操作,还可以实现更复杂的归约逻辑,如字符串连接、对象合并(如案例3)等。
4.4 查找元素 findFirst、findAny
// 1.可结合排序使用,获取第一个元素Optional<User> first = userList.stream().findFirst();// 2.任意元素Optional<User> any = userList.stream().findAny();
4.5 匹配 anyMatch、allMatch、noneMatch
// 1.anyMatch有一个满足条件就返回trueboolean anyMatch= userList.stream().anyMatch(p -> p.getAge() == p.getLevel());// 2.allMatch所有元素都满足条件就返回trueboolean allMatch = userList.stream().allMatch(p -> p.getAge() == p.getLevel());// 3.noneMatch 没有元素满足条件就返回trueboolean noneMatch = userList.stream().noneMatch(p -> p.getAge() == p.getLevel());
4.6 count max min
// 1.获取流中元素的个数,如果List,则与list.size()等效long count = userList.stream().count();// 2.根据age字段获取age最大值对应的对象,返回Optional,可用orElse规避nullUser user = userList.stream().max(Comparator.comparing(User::getAge)).orElse(new User());// 3.同理,根据level字段获取level最小值对应的对象User user6 = userList.stream().min(Comparator.comparing(User::getLevel)).orElse(new User());
五、并行流
Java 8 引入了并行流的概念,允许你以并行方式处理数据集合,以利用多核处理器的优势。你可以通过调用 stream()
方法的并行版本 parallelStream()
来获取一个并行流。
List<String> strings = Arrays.asList("apple", "banana", "cherry", ...);
long count = strings.parallelStream().filter(s -> s.startsWith("a")).count();
注意
:并行流可能会同时处理多个元素,因此流中的操作必须是线程安全的。
六、总结
6.1 Stream的优点:
代码更简洁、偏声明式
的编码风格,更容易体现出代码的逻辑意图逻辑间解耦
,一个stream中间处理逻辑,无需关注上游与下游的内容,只需要按约定实现自身逻辑即可效率高
:并行流场景效率会比迭代器逐个循环更高函数式接口,延迟执行
的特性,中间管道操作不管有多少步骤都不会立即执行,只有遇到终止操作的时候才会开始执行,可以避免一些中间不必要的操作消耗
6.2 Stream的缺点
Stream也不全是优点,在有些方面也有其弊端:
调试不便
代码调测debug不便需要时间适应
程序员从历史写法切换到Stream时,需要一定的学习、适应时间
以上即为java1.8版本Stream处理集合等数据的方法介绍,篇幅较长,仅供参考。
相关文章:
jdk1.8 List集合Stream流式处理
jdk1.8 List集合Stream流式处理 一、介绍(为什么需要流Stream,能解决什么问题?)1.1 什么是 Stream?1.2 常见的创建Stream方法1.3 常见的中间操作1.4 常见的终端操作 二、创建流Stream2.1 Collection的.stream()方法2.2 数组创建流2.3 静态工厂…...
leetcode位运算(1720. 解码异或后的数组)
前言 经过前期的基础训练以及部分实战练习,粗略掌握了各种题型的解题思路。后续开始专项练习。 描述 未知 整数数组 arr 由 n 个非负整数组成。 经编码后变为长度为 n - 1 的另一个整数数组 encoded ,其中 encoded[i] arr[i] XOR arr[i 1] 。例如&am…...
Android 性能优化之卡顿优化
文章目录 Android 性能优化之卡顿优化卡顿检测TraceView配置缺点 StricktMode配置违规代码 BlockCanary配置问题代码缺点 ANRANR原因ANRWatchDog监测解决方案 Android 性能优化之卡顿优化 卡顿检测 TraceViewStricktModelBlockCanary TraceView 配置 Debug.startMethodTra…...
mac电脑显示隐藏文件
方法一: 第一步:打开「终端」应用程序。 第二步:输入如下命令: defaults write com.apple.finder AppleShowAllFiles -boolean true ; killall Finder 第三步:按下「Return」键确认。 现在你将会在 Finder 窗口中…...
深度学习之基础知识整理
现在大语言模型很火,但它的基础仍然是以神经网络为基础的深度学习,不懂神经网络,不了解深度学习,对于大语言模型的二次开发也是整不明白。 那到底需要了解哪些知识?才能看懂深度学习/神经网络的基础模型,想…...
R语言学习笔记9-数据过滤-分组-融合
R语言学习笔记9-数据过滤-分组-融合 数据过滤基础数据过滤条件筛选数据使用dplyr包进行数据操作select 函数filter 函数subset函数 数据分组使用split()进行数据分组使用dplyr包进行数据分组使用data.table包进行数据分组 数据融合使用merge()进行数据融合使用dplyr包进行数据融…...
GESP CCF C++ 八级认证真题 2024年6月
第 1 题 GESP活动期间,举办方从获胜者ABCDE五个人中选出三个人排成一队升国旗,其中A不能排在队首,请问 有多少种排法? A.24 B.48 C.32 D.12 第 2 题 7进制数235转换成3进制数是( )。 A. 11121 B. 11…...
Spring Boot 单元测试什么时候需要添加 @RunWith
建立 Spring Boot 单元测试方法一般依赖于 JUnit4 或 JUnit5 框架。 在高版本的 Spring Boot 中,一般默认用的是 JUnit5。此时通过添加 SpringBootTest 注解,即可成功注入相关的 bean 对象,并进行测试。 import org.junit.jupiter.api.Test…...
鸿蒙OpenHarmony Native API【HiLog】
HiLog Overview Description: HiLog模块实现日志打印功能。 开发者可以通过使用这些接口实现日志相关功能,输出日志时可以指定日志类型、所属业务领域、日志TAG标识、日志级别等。 syscap SystemCapability.HiviewDFX.HiLog Since: 8 Summary Files File …...
Pycharm 和虚拟环境的那些事?
背景: 我既有 python 又有Anaconda Pycharm新建虚拟环境: 只说两种方式 通过Virualenv Environment新建: 这里我们勾选上 Make available to all projects ,之后点击🆗 然后可以发现只有非常少的包,因为没有勾选继承 编译器的包 创建的虚拟环境一般目录如下&…...
rancher2里面的containerd的使用
rancher2使用containerd了,在node上去跑docker命令找不到以前的那些pod了,查了很久才设置好crictl的配置 kubectl get nodes -o wide NAME STATUS ROLES AGE VERSION INTERNAL-IP EXTERNAL-IP O…...
Python数据风险案例54——人工智能热门概念股爬虫分析其价值(三因子模型)
案例背景 人工智能概念如火如荼的夏天,在这个2024年,我觉得需要提早布局一下这个概念。所以我们找一下A股里面人们的人工智能概念股,然后分析他们的数据应用三因子模型,也就是最经典的资本资产定价模型的衍生版去研究他们各自的投…...
【HarmonyOS开发】Navigation使用
简介 Navigation是路由容器组件,包括单栏(Stack)、分栏(Split)和自适应(Auto)三种显示模式。适用于模块内和跨模块的路由切换。 在页面跳转时,应该使用页面路由router,在页面内的页面跳转时,建议使用Navigation达到更好的转场动效…...
计算机网络参考模型与5G协议
目录 OSI七层参考模型OSI模型vsTCP/IP模型TCP/IP协议族的组成 OSI七层参考模型 分层功能应用层网络服务与最终用户的一个接口表示层数据的表示,安全,压缩会话层建立,管理,终止会话传输层定义传输数据的协议端口号,以及流控和差错校验网络层进行逻辑地址寻址,实现不同网路之间的…...
docker自建rustdesk-server远程桌面
rustdesk简介 RustDesk 是一款可以平替 TeamViewer 的开源软件,旨在提供安全便捷的自建方案。 RustDesk 是一款功能齐全的远程桌面应用,具有以下特性: 支持 Windows、macOS、Linux、iOS、Android、Web 等多个平台。支持 VP8 / VP9 / AV1 …...
海外抖音黑屏是网络问题还是硬件问题?
随着海外抖音(TikTok)在全球范围内的普及,越来越多的用户开始体验这一短视频社交平台。然而,不少用户在使用过程中遇到了黑屏问题,这让人不禁疑惑:这究竟是网络问题还是硬件问题? 首先…...
为了实现接口缓存,专门写了个缓存库 f-cache-memory
问题起因 起因是某次发版之后,服务器接口压力过大,当场宕机,排查之后发现有个接口在首页被调十来次(六七年的老项目了,都是泪呀),后端反馈这个接口的sql很复杂,很耗性能,…...
actual combat 35 —— es
一、windows中es执行步骤 参考:https://blog.csdn.net/qq_21197507/article/details/115076913 下es安装包下es前端gitHub代码,然后npm -i安装,npm run start 启动安装kibana 二、遇到的问题 1. 第二步安装前端代码依赖报错 npm ERR! co…...
android R ext4 image打包脚本介绍
一、Android R打包指令使用介绍 (1)mkuserimg_mke2fs #./mkuserimg_mke2fs --help usage: mkuserimg_mke2fs [-h] [--android_sparse] [--journal_size JOURNAL_SIZE][--timestamp TIMESTAMP] [--fs_config FS_CONFIG][--product_out PRODUCT_OUT][--b…...
美式键盘 QWERTY 布局的来历
注:机翻,未校对。 The QWERTY Keyboard Is Tech’s Biggest Unsolved Mystery QWERTY 键盘是科技界最大的未解之谜 It’s on your computer keyboard and your smartphone screen: QWERTY, the first six letters of the top row of the standard keybo…...
ETL数据同步之DataX,附赠一套DataX通用模板
今天跟大家分享数据同步datax的模板,小伙伴们简单直接借鉴使用。 还记得上一篇关于大数据DS调度工具的分享嘛? 主流大数据调度工具DolphinScheduler之数据ETL流程-CSDN博客 里面的核心就是采用了DATAX的数据同步原理。 一,什么是DataX D…...
[论文笔记] CT数据配比方法论——1、Motivation
我正在写这方面的论文,感兴趣的可以和我一起讨论!!!!!! Motivation 1、探测原有模型的配比: 配比 与 ppl, loss, bpw, benchmark等指标 之间的关系。 2、效果稳定的配比:配比 与 模型效果 之间的规律。 Experiments 1、主语言(什么语言作为主语言,几种主语言?…...
某4G区域终端有时驻留弱信号小区分析
这些区域其实是长时间处于连接态的电信卡4G终端更容易出现。 出现问题时都是band1 100频点下发了针对弱信号的1650频点的连接态A4测量事件配置(其阈值为-106)。而这个条件很容易满足,一旦下发就会切到band3 1650频点。 而1650频点虽然下发ban…...
【体外诊断】ARM/X86+FPGA嵌入式计算机在免疫分析设备中的应用
体外诊断 信迈提供基于Intel平台、AMD平台、NXP平台的核心板、2.5寸主板、Mini-ITX主板、4寸主板、PICO-ITX主板,以及嵌入式准系统等计算机硬件。产品支持GAHDMI等独立双显,提供丰富串口、USB、GPIO、PCIe扩展接口等I/O接口,扩展性强…...
Linux上启动和停止jar
linux 后台运行jar 在Linux系统中,要想让jar包在后台运行,可以使用nohup命令和&符号。nohup命令可以使进程在后台不受挂起信号影响的执行,而&符号则是将任务放入后台执行。 以下是一个简单的命令示例,它将启动一个jar包…...
浏览器缓存:强缓存与协商缓存实现原理有哪些?
1、强缓存:设置缓存时间的,那么在这个时间内浏览器向服务器发送请求更新数据,但是服务器会让其从缓存中获取数据。 可参考:彻底弄懂强缓存与协商缓存 - 简书 2、协商缓存每次都会向浏览器询问,那么是怎么询问的呢&…...
持续集成04--Jenkins结合Gitee创建项目
前言 在持续集成/持续部署(CI/CD)的旅途中,Jenkins与版本控制系统的紧密集成是不可或缺的一环。本篇“持续集成03--Jenkins结合Gitee创建项目”将引导如何将Jenkins与Gitee(一个流行的Git代码托管平台)相结合ÿ…...
【Node.js基础02】fs、path模块
目录 一:fs模块-读写文件 1 加载fs模块对象 2 读制定文件内容文件 3 向文件中写入内容 二:path模块-路径处理 1 问题引入 2 __dirname内置变量 使用方法 一:fs模块-读写文件 fs模块封装了与本机文件系统交互方法和属性 1 加载fs模块…...
牛客TOP101:单链表的排序
文章目录 1. 题目描述2. 解题思路3. 代码实现 1. 题目描述 2. 解题思路 按我们以往的排序算法来看,针对链表来说都是太不合适,因为很多都会出现指针前移后移,后移还好说,前移对于链表来说就太难了,而且大部分都是某一个…...
数据可视化配色新工具,颜色盘多达2500+类
好看的配色,不仅能让图表突出主要信息,更能吸引读者,之前分享过很多配色工具,例如, 👉可视化配色工具:颜色盘多达3000+类,数万种颜色! 本次再分享一个配色工具pypalettes,颜色盘多达2500+类。 安装pypalettes pip install pypalettes pypalettes使用 第1步,挑选…...
网站seo怎样做/平台营销
创建统一管理的,具备灵活性的云原生生产部署来部署一个个性化的数据库即服务(DBaaS)。-- Jonathan S. Katz通过在 Kubernetes 上运行 PostgreSQL 数据库,你能创建统一管理的,具备灵活性的云原生生产部署应用来部署一个个性化的数据库即服务为…...
建设部门户网站/自己怎么做关键词优化
一、前言最近公司在预研设备app端与服务端的交互方案,主要方案有服务端和app端通过阿里iot套件实现消息的收发;服务端通过极光推送主动给app端推消息,app通过rest接口与服务端进行交互;服务端与app通过mqtt消息队列来实现彼此的消…...
一般课程网站要怎么做/百度统计代码安装位置
interruptedException: http://blog.csdn.net/srzhz/article/details/6804756 1. 处于sleeping,awaiting,或是倍占用的线程(阻塞状态),中断就会抛出interruptedException: 但是并没有被职位,而且也不会中断…...
做网站引流做什么类型的网站最好/百度搜索量怎么查
1、明确需求 实战工作中经常会请求各种各样的接口,例如 •微信网页授权接口 • 高德周边检索接口 •发现:下图是根据接口文档开发后的代码,发现代码可读性差(ps. 参数的作用不详) • 解决:通过http_buil…...
东营有网站/企业宣传推广方案
...