到今天了你还不会集合的Stream操作吗?你要out了
Java8的两个重大改变,一个是Lambda表达式,另一个就是本节要讲的Stream API表达式。Stream 是Java8中处理集合的关键抽象概念,它可以对集合进行非常复杂的查找、过滤、筛选等操作,在新版的JPA中,也已经加入了Stream。
一. Stream操作步骤
1.1 Stream有如下三个操作步骤:
第一步:创建流
从一个数据源,如集合、数组中获取流。
第二步:进行中间操作
一个流后面可以跟0个或多个中间操作,多个中间操作可以连接起来形成一条流水线。中间操作通常在没有结束操作之前是不会被触发的。
第三步:获取结果(结束操作)
一个流只能有一个结束操作,当这个操作执行后,前面的中间操作会被触发,此时流就再无法被使用。Stream中几乎所有返回是void方法都是结束操作。
1.2 Stream的特征
第一步:创建流
- 流不会存储值,通过管道的方式获取值。
- 对流的操作会生成一个结果,不过并不会修改底层的数据源
- 一个流只能使用一次
1.3 入门小案例
假设有一个Person类和一个Person列表,现在有两个需求:1)找到年龄大于18岁的人并输出;2)找出所有中国人的数量。
@Data
class Person {
private String name;
private Integer age;
private String country;
private char sex;
public Person(String name, Integer age, String country, char sex) {
this.name = name;
this.age = age;
this.country = country;
this.sex = sex;
}
}
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪",18,"中国",'F'));
personList.add(new Person("Tom",24,"美国",'M'));
personList.add(new Person("Harley",22,"英国",'F'));
personList.add(new Person("向天笑",20,"中国",'M'));
personList.add(new Person("李康",22,"中国",'M'));
personList.add(new Person("小梅",20,"中国",'F'));
personList.add(new Person("何雪",21,"中国",'F'));
personList.add(new Person("李康",22,"中国",'M'));
在JDK8以前,我们可以通过遍历列表来完成。但是在有了Stream API后,可以这样来实现:
public static void main(String[] args) {
// 1)找到年龄大于18岁的人并输出;
personList.stream().filter((p) -> p.getAge() > 18).forEach(System.out::println);
System.out.println("-------------------------------------------");
// 2)找出所有中国人的数量
long chinaPersonNum = personList.stream().filter((p) -> p.getCountry().equals("中国")).count();
System.out.println("中国人有:" + chinaPersonNum + "个");
}
输出结果:
在这个例子中,personList.stream()是创建流,filter()属于中间操作,forEach、count()是终止操作。
二. 创建流
2.1 创建有限流
有限流的意思就是创建出来的流是基于一个已经存在的数据源,也就是说流在创建的时候数据源的个数已经基本确定了。
Stream.of()
:Stream类的静态方法。Collection实例.stream()
:返回此集合作为数据源的流。Collection实例.parallelStream()
:返回此集合作为数据源的并行流。Arrays.stream()
:数组转换为流。
List<String> list = new ArrayList<>();
Stream<String> stream1 = list.stream();
String[] arr=new String[15];
Stream<String> stream2 = Arrays.stream(arr);
Stream<String> stream3 = Stream.of("a", "b", "c");
2.2 创建无限流
无限流相对于有限流,并没有一个特定定的数据源,它是通过循环执行"元素生成器"来不断产生新元素的。这种方式在日常开发中相对少见。
Stream.iterate(T seed, UnaryOperator<T> f)
:创建一个无限但有序的流 ,seed传入的是迭代器的种子(起始值),f传入的是迭代函数。
//这个流从0开始,每一个元素加3
Stream<Integer> stream4 = Stream.iterate(0, i -> i + 3);
//循环打印流中的元素(如果不停止运行,程序会无限输出下去)
stream4.forEach(System.out::println);
//----------上述Lambda表达式实际上是下列语法的一个简化写法,后文不再提供Lambda表达式转换,如果看不懂的话可以先去熟悉Lambda表达式的用法------
Stream.iterate(0, new UnaryOperator<Integer>() {
@Override
public Integer apply(Integer i) {
return i + 3;
}
});
Stream.generate(Supplier<T> s)
:传入的是一个“元素生成器”,Supplier是一个函数式接口,它用于在无输入参数的情况下,返回一个结果值。
//创建一个无限流,这个流中每一个元素都是Math.random()方法生成的
Stream<Double> stream5 = Stream.generate(() -> Math.random());
stream5.forEach(System.out::println);
//----------上述Lambda表达式由于只调用了一个 Math.random()方法,实际上能够更加精简----
Stream<Double> stream5 = Stream.generate(Math::random);
三. Stream中间操作
3.1 筛选与切片
- filter:从流中排除某些操作;
- limit(n):截断流,使其元素不超过给定对象
- skip(n):跳过元素,返回一个扔掉了前n个元素的流,若流中元素不足n个,则返回一个空流,与limit(n)互补
- distinct:筛选,通过流所生成元素的hashCode()和equals()去除重复元素。
3.1.1 filter
//保留流中person.getAge()==20的元素
personList.stream().filter(person -> person.getAge() == 20).forEach(System.out::println);
输出结果为:
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
3.1.2 limit
personList.stream().limit(2).forEach(System.out::println);
输出结果为:
Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=Tom, age=24, country=美国, sex=M)
3.1.3 skip
personList.stream().skip(1).forEach(System.out::println);
输出结果为:
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
Person(name=李康, age=22, country=中国, sex=M)
3.1.4 distinct
personList.stream().distinct().forEach(System.out::println);
输出结果为:
Person(name=欧阳雪, age=18, country=中国, sex=F)
Person(name=Tom, age=24, country=美国, sex=M)
Person(name=Harley, age=22, country=英国, sex=F)
Person(name=向天笑, age=20, country=中国, sex=M)
Person(name=李康, age=22, country=中国, sex=M)
Person(name=小梅, age=20, country=中国, sex=F)
Person(name=何雪, age=21, country=中国, sex=F)
3.2 映射
3.2.1 map
map映射是接收一个Function接口的实例,它将Stream中的所有元素依次传入进去,Function.apply
方法将原数据转换成其他形式的数据。
例一:
假如,我们需要将List<String>
所有元素转化为大写,我们可以这么做:
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "ddd");
stream.map(new Function<String, String>() {
@Override
public String apply(String s) {
return s.toUpperCase();
}
}).forEach(System.out::println);
//上面的匿名内部类可以精简为Lambda表达式形式
stream.map(s -> s.toUpperCase()).forEach(System.out::println);
//采用方法引用,更进一步精简Lambda表达式
stream.map(String::toUpperCase).forEach(System.out::println);
流中的每一个元素都经过了Function
实例apply
方法的处理,将其转换为了大写。
例二:
假如我们需要取出List<Person>
中每一个Person中的姓名取出来并打印出来:
List<Person> personList = new ArrayList<>();
personList.add(new Person("欧阳雪", 18, "中国", 'F'));
personList.add(new Person("Tom", 24, "美国", 'M'));
personList.add(new Person("Harley", 22, "英国", 'F'));
personList.add(new Person("向天笑", 20, "中国", 'M'));
personList.add(new Person("李康", 22, "中国", 'M'));
personList.add(new Person("小梅", 20, "中国", 'F'));
personList.add(new Person("何雪", 21, "中国", 'F'));
personList.add(new Person("李康", 22, "中国", 'M'));
personList.stream().map(new Function<Person, String>() {
@Override
public String apply(Person person) {
return person.getName();
}
}).forEach(System.out::println);
//上面的匿名内部类可以精简为Lambda表达式形式
personList.stream().map(person -> person.getName()).forEach(System.out::println);
//采用方法引用,更进一步精简Lambda表达式
stream.map(Person::getName).forEach(System.out::println);
3.2.2 flatMap
flatMap接收一个函数作为参数,将流中的每一个值转换成另一个流,然后把所有流合并在一起。
例一:
我们需要将List<String>
中的每一个字符按顺序打印出来。我们先试着用map映射达到需求:
//使用map也能实现这个需求
@Test
public void testFlatMap() {
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "ddd");
Stream<Stream<Character>> streamStream = stream.map(s -> toCharacterStream(s));
streamStream.forEach(s->s.forEach(System.out::println));
}
public static Stream<Character> toCharacterStream(String str) {
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
我们通过map()
将原先流中每一个String
映射为Stream<Character>
然后重新放入原先的流中,此时流中的每一个元素都是一个Stream<Character>
,然后我们遍历外层流得到每一个子流,然后子流forEach输出每一个字符。
可以看到使用map
映射的时候,如果返回Stream
,那这些Stream仍然是相互独立的;但是flatMap
映射会将返回的流合并为一个流。
@Test
public void testFlatMap2() {
Stream<String> stream = Stream.of("aaa", "bbb", "ccc", "ddd");
//重点对比这一行,map返回的是Stream<Stream<Character>>,flatMap返回的是Stream<Character>
Stream<Character> characterStream = stream.flatMap(s -> toCharacterStream(s));
characterStream.forEach(System.out::println);
}
public static Stream<Character> toCharacterStream(String str) {
List<Character> list = new ArrayList<>();
for (char c : str.toCharArray()) {
list.add(c);
}
return list.stream();
}
你品,你细细的品!!
3.3 排序
3.3.1 自然排序
自然排序需要流中的实例实现了Comparable
接口。
personList.stream().sorted().forEach(System.out::println);
3.3.2 定制排序
定制排序,需要传入一个Comparator
personList.stream().sorted((o1, o2) -> o1.getAge()-o2.getAge()).forEach(System.out::println);
四. 终止操作
4.1 查找与匹配
- allMatch:检查是否匹配所有元素,返回boolean
- anyMatch:检查是否至少匹配一个元素,返回boolean
- noneMatch:检查是否没有匹配所有元素,返回boolean
- findFirst:返回第一个元素
- findAny:返回当前流中任意元素
- count:返回元素中元素的总个数
- max:返回流中的最大值
- min:返回流中的最小值
- forEach:循环
4.2 规约(reduce)
reduce是将流中的元素反复结合起来,得到一个最终值:
//没有初始值的规约
Optional<T> reduce(BinaryOperator<T> accumulator);
//identity是初始值,accumulator是一个二元运算
T reduce(T identity, BinaryOperator<T> accumulator);
例一
数组中所有元素的求和:
Stream<Integer> stream = Stream.of(1, 2, 3, 4, 5, 6);
Integer sum = stream.reduce(0, (integer, integer2) -> integer + integer2);
//Integer sum = stream.reduce(0, Integer::sum); 与上面具有想用意义
System.out.println(sum);
例二
计算所有人的年龄总和:
Optional<Integer> reduce = personList.stream().map(person -> person.getAge()).reduce(Integer::sum);
System.out.println(reduce.get());
4.3 收集(collect)
collect()
将流转化为其他形式。接收一个Collector
(收集器)接口的实现,用于收集流中的元素。Collector
接口中的方法决定了如何对流进行收集操作,Collectors
类提供了很多静态方法,可以方便地创建常用的收集器:
4.3.1 收集为数组
Person[] personArray = personList.stream().toArray(Person[]::new);
4.3.2 收集成集合
Collectors.toList()
:将所有元素收集到一个List中
List<Person> persons = personList.stream().collect(Collectors.toList());
Collectors.toSet()
:将所有元素收集到一个Set中
Set<Person> persons = personList.stream().collect(Collectors.toSet());
Collectors.toMap()
:将所有元素收集到一个Map中
//将Person实例的name属性作为Key,person对象作为value放入Map中
Map<String, Person> collect = personList.stream().distinct().collect(Collectors.toMap(person -> person.getName(),o -> o));
Collectors.toCollection()
:将所有元素放入集合
HashSet<Person> collect = personList.stream().collect(Collectors.toCollection(HashSet::new));
4.3.3 收集聚合信息(类似于SQL中的聚合函数)
Collectors.averagingInt()
:收集所有元素求平均值。
//获取所有Person的平均年龄
Double average = personList.stream().collect(Collectors.averagingInt(Person::getAge));
Collectors.counting()
:计算元素总数量
Long count = personList.stream().collect(Collectors.counting());
Collectors.summing()
:计算元素总和
//求出年龄的总和
Integer sum = personList.stream().collect(Collectors.summingInt(Person::getAge));
Collectors.maxBy()
:最大值(与max()方法效果一样)Collectors.minBy()
:最小值
//求出年龄最大的人
Optional<Person> max = personList.stream().collect(Collectors.maxBy((o1, o2) -> o1.getAge() - o2.getAge()));
Collectors.summarizingInt
:可以获取所有的聚合信息。
//获取年龄的所有聚合信息
IntSummaryStatistics summary = personList.stream().collect(Collectors.summarizingInt(Person::getAge));
System.out.println(summary.getMax());
System.out.println(summary.getAverage());
System.out.println(summary.getCount());
System.out.println(summary.getMin());
4.3.4 分组收集(类似于SQL中的group by语句)
//分类器函数将输入元素映射到键(单级分组)
groupingBy(Function<? super T, ? extends K> classifier)
//多级分组。downstream实现下级分组
groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream)
//多级分组,并且指定当前分组创建Map的方法
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream)
例如我们需要按照年龄进行分组:
//按照年龄进行分组
Map<Integer, List<Person>> collect = personList.stream().collect(Collectors.groupingBy(person -> person.getAge()));
Map<Integer, List<Person>> collect = personList.stream().collect(Collectors.groupingBy(Person::getAge, HashMap::new, Collectors.toCollection(ArrayList::new)));
{
18: [{
"age": 18,
"country": "中国",
"name": "欧阳雪",
"sex": "F"
}],
20: [{
"age": 20,
"country": "中国",
"name": "向天笑",
"sex": "M"
}, {
"age": 20,
"country": "中国",
"name": "小梅",
"sex": "F"
}],
...
}
多级分组
我们可以通过传入下级分组器,来实现多级分组。例如我们需要先按照年龄进行分组,然后再按照国籍进行分组:
Map<Integer, Map<String, List<Person>>> collect = personList.stream().collect(
Collectors.groupingBy(person -> person.getAge(), Collectors.groupingBy(o -> o.getCountry())));
//或者这么写也可以
Map<Integer, Map<String, List<Person>>> collect = personList.stream().collect(
Collectors.groupingBy(Person::getAge, Collectors.groupingBy(Person::getCountry)));
如果我们每一级的集合对象都需要自定义,我们可以这样做:
HashMap<Integer, TreeMap<String, LinkedList<Person>>> collect = personList.stream().collect(
Collectors.groupingBy(Person::getAge,
HashMap::new, Collectors.groupingBy(Person::getCountry,TreeMap::new,
Collectors.toCollection(LinkedList::new))));
{
18: {
"中国": [{
"age": 18,
"country": "中国",
"name": "欧阳雪",
"sex": "F"
}]
},
22: {
"中国": [{
"age": 22,
"country": "中国",
"name": "李康",
"sex": "M"
}, {
"age": 22,
"country": "中国",
"name": "李康",
"sex": "M"
}],
"英国": [{
"age": 22,
"country": "英国",
"name": "Harley",
"sex": "F"
}]
}
}
4.3.5 分区收集
分区收集能将符合条件的放在一个集合中,不符合条件的放在另一个集合中
//将年龄大于等于20的分为一组,将年龄小于20的分为一组(实际上用groupBy也能实现)
Map<Boolean, List<Person>> collect = personList.stream().collect(Collectors.partitioningBy(person -> person.getAge() >= 20));
4.3.6 拼接字符串
Collectors.joining
收集器按顺序将输入元素连接到一个字符串中:
//将姓名拼成字符串中间用逗号隔开,首尾用大括号括起来
String str = personList.stream().map(Person::getName).collect(Collectors.joining(",","{","}"));
{欧阳雪,Tom,Harley,向天笑,李康,小梅,何雪,李康}
4.4 循环
- foreach
personList.stream().forEach(System.out::println);
五. 并行流
前面我么所将的流的操作都建立在串行流的基础上,在数据量小的情况下没有任何问题,但是一旦数据量多起来,单线程的效率问题就会凸显。
不同于串行流,并行流底层使用了Fork-Join框架,将任务分派到多个线程上执行,这样可以大大提高CPU资源的利用率。
5.1 如何创建并行流
在创建时直接创建并行流:
Collection实例.parallelStream();
将串行流转化为并行流:
Stream实例.parallel();
将并行流转化为串行流:
Stream实例.sequential();
5.2 串行流和并行流的对比
我们分别使用串行流和并行流进行一百亿个数的累加:
串行流:
long start = System.currentTimeMillis();
LongStream stream = LongStream.rangeClosed(0, 10000000000L);
long reduce = stream.reduce(0, (left, right) -> left + right);
long end = System.currentTimeMillis();
System.out.println("付费:" + (end - start) );
通过测试我们发现,串行流需要55s,通过任务管理器也能发现CPU资源并没有充分利用。
并行流:
long start = System.currentTimeMillis();
LongStream stream = LongStream.rangeClosed(0, 100000000000L);
long reduce = stream.parallel().reduce(0, (left, right) -> left + right);
long end = System.currentTimeMillis();
System.out.println("付费:" + (end - start) );
并行流花费:20S,并行流在执行时能够充分利用CPU资源。
到今天了你还不会集合的Stream操作吗?你要out了的更多相关文章
- Python【第二课】 字符串,列表,字典,集合,文件操作
本篇内容 字符串操作 列表,元组操作 字典操作 集合操作 文件操作 其他 1.字符串操作 1.1 字符串定义 特性:不可修改 字符串是 Python 中最常用的数据类型.我们可以使用引号('或&quo ...
- 简洁又快速地处理集合——Java8 Stream(下)
上一篇文章我讲解 Stream 流的基本原理,以及它与集合的区别关系,讲了那么多抽象的,本篇文章我们开始实战,讲解流的各个方法以及各种操作 没有看过上篇文章的可以先点击进去学习一下 简洁又快速地处理集 ...
- 简洁又快速地处理集合——Java8 Stream(上)
Java 8 发布至今也已经好几年过去,如今 Java 也已经向 11 迈去,但是 Java 8 作出的改变可以说是革命性的,影响足够深远,学习 Java 8 应该是 Java 开发者的必修课. 今天 ...
- 【转载】 C#中使用Sum方法对List集合进行求和操作
在C#的List操作中,有时候我们需要对List集合对象的某个属性进行求和操作,此时可以使用Lambda表达式中的Sum方法来快速实现此求和操作,使用Sum方法可使代码简洁易读,并且省去写for循环或 ...
- 【Go语言】集合与文件操作
本文目录 1.数据集合的主要操作 1_1.字典的声明 1_2.字典的初始化和创建 1_3.字典的访问和操作 1_4.其他类型的数据集 2.文件操作 2_1.文件操作概述os包和path包 2_2.文件 ...
- java集合之链式操作
如果用过js/jquery.groovy等语言,大概对这样的代码比较熟悉: [1,2,3].map(function(d){...}).grep(function(d){...}).join(',') ...
- java 集合框架(List操作)
/*list 基本操作 * * List a=new List(); * 增 * a.add(index,element);按指定位置添加,其余元素依次后移 * addAll(index,Collec ...
- redis:order set有序集合类型的操作(有序集合)
1. order set有序集合类型的操作(有序集合) 有序集合是在无序集合的基础上加了一个排序的依据,这个排序依据叫score,因此声明一个集合为有序集合的时候要加上score(作为排序的依据) 1 ...
- redis:set集合类型的操作(无序集合)
1. set集合类型的操作(无序集合) 集合具有无序性(没有顺序).确定性(描述是确定的).唯一性(没有重复的元素) 1.1. sadd key member [member ...] 语法:sadd ...
- [PY3]——内置数据结构(6)——集合及其常用操作
集合及其常用操作Xmind图 集合的定义 # set( ) # {0,1,2} //注意不能用空的大括号来定义集合 # set(可迭代对象) In [1]: s=set();type ...
随机推荐
- tkinter属性--转载Tynam Yang
一.主要控件 1.Button 按钮.类似标签,但提供额外的功能,例如鼠标掠过.按下.释放以及键盘操作事件 2.Canvas 画布.提供绘图功能(直线.椭圆.多边形.矩形) 可以包含图形或位图 3.C ...
- 【#HDC2022】HarmonyOS体验官活动正式开启,赶快投稿赢限量奖品吧!
1. [活动简介] HDC 2022 于11月4日线上线下正式开启.历时一年,在无数开发者的共同努力下,我们汇聚了HarmonyOS生态的新成果.新体验.新开放能力,邀你参与到HarmonyOS的 ...
- CentOS 6.5编译安装httpd-2.4.7
CentOS 6.5编译安装httpd-2.4.7 CentOS 编译安装 Apache 2.4 准备: [root@NFSServer ~]# yum groupinstall "Deve ...
- 第三課:信道学习Source Connect Reader & Destinations File Writer
第一步: 切换到主信道(Channels)界面,右键点击新建信道(New Channel) 第二步 : 下面是设置一些信道概要(Summary)信息 其中summary(概要) 界面主要包含 信道名称 ...
- 力扣584(MySQL)-寻找用户推荐人(简单)
题目: 给定表 customer ,里面保存了所有客户信息和他们的推荐人. 写一个查询语句,返回一个客户列表,列表中客户的推荐人的编号都 不是 2. 对于上面的示例数据,结果为: 解题思路: 本题最 ...
- 力扣61(java&python)-旋转链表(中等)
题目: 给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置. 示例1: 输入:head = [1,2,3,4,5], k = 2 输出:[4,5,1,2,3] 示例2: 输 ...
- EasyNLP玩转文本摘要(新闻标题)生成
简介: 本⽂将提供关于PEGASUS的技术解读,以及如何在EasyNLP框架中使⽤与PEGASUS相关的文本摘要(新闻标题)生成模型. 作者:王明.黄俊 导读 文本生成是自然语言处理领域的一个重要研究 ...
- InnoDB之UNDO LOG介绍
简介: undo log是InnoDB事务特性的重要组成部分.当对记录做增删改操作就会产生undo记录,undo记录会记录到单独的表空间中. 本文将从代码层面对undo log进行一个简单的介绍:主要 ...
- [FAQ] PHP+Nginx 架构网站 502 和 504 问题
502 Bad Gateway:作为网关或者代理工作的服务器尝试执行请求时,从上游服务器接收到无效的响应.504 Gateway Time-out:作为网关或者代理工作的服务器尝试执行请求时,未能及时 ...
- 用 C# 写脚本 如何输出文件夹内所有文件名
大部分在 Windows 下的脚本都是使用 bat 或 cmd 写的,这部分的脚本对我来说可读性不好.这个可读性也是很主观的,对我来说用 C# 写脚本的可读性很强,但是换个小伙伴就不是了.在 .NET ...