Java 常见Stream操作
文章目录
- Java 常见Stream操作
- 基础知识点
- 流概念
- 串行流和并行流
- 数值流
- 对象转换
- Comparator和Collectors
- 语法糖
- 数据结构互转
- int[]互转List
- int[] []互转List< List >
- int[] []互转List< int[] >
- char[]转List
- int[] []展平为List
- List< List >展平为List
- Object[]互转List
- Object[] []互转List< List >
- 常用操作
- 聚合
- sum(数组求和)
- count(统计个数)
- max,min(寻找最值)
- avg(计算平均值)
- 遍历
- forEach(串行流遍历)
- forEach(并行流遍历)
- 筛选
- filter(过滤)
- distinct(去重)
- limit和skip(截流)
- 排序
- sort(排序)
- 映射
- map(映射)
- flatMap(展平)
- 归约
- reduce(汇总,归约)
- 匹配
- allMatch(全部匹配)
- noneMatch(全不匹配)
- anyMatch(存在匹配)
- 收集
- collect(收集)
- Collectors.groupingBy(分组)
- Collectors.groupingBy(多重分组)
- Collectors.groupingBy(分组统计)
基础知识点
转载请标明出处:鸭梨的药丸哥
流概念
Java 8 API添加了一个新的抽象称为流 Stream
,可以使用链式编程的方式对数据进行批处理。
整个流操作就是一条流水线,将元素放在流水线上一个个地进行处理。和迭代器又不同的是,Stream 可以并行化操作(并行流),迭代器只能命令式地、串行化操作。
串行流和并行流
Java提供了两种流,分别是:串行流和并行流。性能上并行流更好,但是需要考虑线程安全问题。
- Stream:串行流,单线程,线程安全,适合阻塞任务
- parallelStream:并行流,多线程,线程不安全,其底层使用Fork/Join框架实现
数值流
Java提供了三种数值流,是专门对数值进行操作的,分别是IntStream,LongStream,DoubleStream,相比普通的流,数值流提供了格外的方法。
数值流主要针对原始数据类型,分别是 int,double,long。
如果使用普通的流进行求和,如:
int sum = list.stream().map(User::getAge).reduce(0, Integer::sum);
操作中存在int的包装和解包(int和Integer的互转),影响效率。
如果我们通过int sum = list.stream().mapToInt(User::getAge).sum();
就可以避免int的包装和解包装。
流和数值流的转换:
- mapToInt(T -> int) : return IntStream
- mapToDouble(T -> double) : return DoubleStream
- mapToLong(T -> long) : return LongStream
对象转换
在Java中数组是对象,想要int[]直接转List是不能的,因为一个对象是不能直接转换为一个列表的,所以需要中间封装。
int[]转List,需要走以下几步:
-
int[]
封装成IntStream
-
IntStream
转Stream<Integer>
-
Stream<Integer>
转List
char[]转List,因为没有CharStream,所以需要使用String做中间封装,需要走以下几步:
-
char[]
封装成String
-
String
转IntStream
-
IntStream
转List
int[] []转List< List>,因为int[]是被看作对象,所以需要使用map操作将每一个int[]转换为List,需要走以下几步:
-
int[][]
封装成Stream<int[]>
-
Stream<int[]>
使用map
函数对int[]
元素转List
,变成Stream<List<Integer>>
-
Stream<List<Integer>>
转List<List>
巧用flatMap去展平二维数组,具体操作如下:
-
int[][]
封装成Stream<int []>
-
Stream<int []>
调用flatMap展平数组
巧用Map去遍历数组中元素,具体操作如下:
- 从集合
Collection
接口的实现类获取Stream
- 在
Stream
中使用map
函数对元素进行操作 - 使用
.collect(Collectors.toList())
再次变成list
Comparator和Collectors
- Collectors:一个工具类,提供集合的相关方法
- Comparator:一个函数式接口,提供一些对比的方法
语法糖
Java提供了很多语法糖,这里只是简单介绍一下
lambda语法,主要有以下几种
- o->{} //对象->{函数体}
- (k,v)->{} //(键,值)->{函数体}
- o->t //对象->返回值
- 类::方法 //代表使用某指定方法
数据结构互转
int[]互转List
注意:long,double这种数值型数组同理
《阿里巴巴 Java 开发手册》的描述如下:
使用集合转数组的方法,必须使用集合的 toArray(T[] array),传入的是类型完全一致、长度为 0 的空数组。
//int[]转List
int[] ints = new int[5];
//方法一
List<Integer> collect = Arrays.stream(ints)
.boxed()
.collect(Collectors.toList());
//方法二
List<Integer> collect = Arrays.stream(ints)
.mapToObj((Integer::new))
.collect(Collectors.toList());
//List转int[]
List<Integer> arr = new ArrayList<>();
//方法一
Integer[] integers = arr.toArray(new Integer[0]);
//方法二
int[] ints = arr.stream()
.mapToInt(Integer::new)
.toArray();
int[] []互转List< List >
注意:long,double这种数值型数组同理
//int[][]转List<List>
int[][] ints = new int[5][5];
//方法一
List<List<Integer>> collect = Arrays.stream(ints)
.map(o -> Arrays.stream(o).boxed().collect(Collectors.toList()))
.collect(Collectors.toList());
//方法二
List<List<Integer>> collect = Arrays.asList(ints)
.stream()
.map(o -> Arrays.stream(o).boxed().collect(Collectors.toList()))
.collect(Collectors.toList());
//List<List>转int[][]
List<List> arr = new ArrayList<>();
//方法一
int[][] objects = (int[][]) arr.toArray();
int[] []互转List< int[] >
注意:long,double这种数值型数组同理
//int[][]转List<int[]>
int[][] ints = new int[5][5];
List<int[]> collect = Arrays.stream(ints)
.collect(Collectors.toList());
//List<int[]>转int[][]
List<int[]> list = new ArrayList<>();
int[][] ints = (int[][])list.toArray();
char[]转List
char[] chars = new char[0];
//char[]转List
List<Character> collect = new String(chars).chars()
.mapToObj(c -> (char) c)
.collect(Collectors.toList());
//List转char[]
Character[] characters = collect.toArray(new Character[collect.size()]);
int[] []展平为List
//int[][]转List<List>
int[][] ints = new int[0][0];
List<Integer> collect = Arrays.stream(ints)
.flatMap(arr->Arrays.stream(arr).boxed())
.collect(Collectors.toList());
List< List >展平为List
List<List> arr = new ArrayList<>();
List<?> collect = arr.stream()
.flatMap(list -> (Stream<?>) list.stream())
.collect(Collectors.toList());
Object[]互转List
//Object[]转List
Object[] objects = new Object[0];
//方法一
List<Object> collect = Arrays.stream(objects)
.collect(Collectors.toList());
//方法二
List<Object> collect = Arrays.asList(objects);
//List转Object[]
List<Object> arr = new ArrayList<>();
Object[] objects = arr.toArray();
Object[] []互转List< List >
//object[][]转List<List>
Object[][] objects = new Object[0][0];
//方法一
List<List<Object>> collect = Arrays.stream(objects)
.map(o -> Arrays.stream(o).collect(Collectors.toList()))
.collect(Collectors.toList());
//方法二
List<List<Object>> collect = Arrays.asList(objects)
.stream()
.map(Arrays::asList)
.collect(Collectors.toList());
//List<List>转Object[][]
List<List> list = new ArrayList<>();
Object[][] objects = (Object[][])list.toArray();
常用操作
聚合
sum(数组求和)
double等同理
int[] ints = {1,2,3,4,5};
int sum = Arrays.stream(ints).sum();
count(统计个数)
count方法用来统计当前流中元素的个数,一般是在一些流操作(过滤,去重)后才使用。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
long count = users.stream().count();
max,min(寻找最值)
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//年龄最大的user
User maxuser = users.stream()
.max(Comparator.comparingInt(User::getAge))
.get();
//年龄最小的user
User minuser = users.stream()
.min(Comparator.comparingInt(User::getAge))
.get();
avg(计算平均值)
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12,"男"));
users.add(new User("jack",14,"男"));
users.add(new User("tom",16,"男"));
//计算年龄均值
double avg = users.stream()
.mapToInt(User::getAge)
.average()
.getAsDouble();
遍历
forEach(串行流遍历)
串行Stream遍历数据是安装当前流中数据的顺序,Stream()
方法返回串行流,串行Stream按照流中当前数据的顺序进行遍历。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//遍历
users.stream().forEach(o->{
System.out.println(o);
});
forEach(并行流遍历)
并行流遍历数据不一定按照流中数据的顺序,parallelStream()
方法返回并行流,并行流相比串行流效率更高,但是无法确保数据顺序,在数据顺序不做要求的操作中可以使用并行流提高效率。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
users.parallelStream().forEach(o->{
System.out.println(o);
});
筛选
filter(过滤)
使用filter
方法过滤数据,保留为 true
的元素,过滤false
的数据。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//过滤掉不是ste的user
List<User> collect = users.stream()
.filter(user -> user.getName().equals("ste"))//返回true时数据被保留
.collect(Collectors.toList());
distinct(去重)
distinct()
依赖于类的equals 方法,对比对象值时,请记得重写equals方法,否则只是对比引用值。
//User类已经重写equals方法
ArrayList<User> users = new ArrayList<>();
users.add(new User("jack",12));
users.add(new User("jack",12));
users.add(new User("tom",12));
//去重后的数据
List<User> collect = users.stream()
.distinct()
.collect(Collectors.toList());
limit和skip(截流)
limit和skip一般用来截取流中元素
//截取前n个元素
List<User> collect = users.stream()
.limit(2)
.collect(Collectors.toList());
//跳过前n个元素
List<User> collect2 = users.stream()
.skip(1)
.collect(Collectors.toList());
排序
sort(排序)
排序又两个方法,一是sort()
,二是sorted(Comparator<? super T> comparator)
。
-
sort()
使用对象的compareTo()
方法(需要实现Comparable接口) -
sorted(Comparator<? super T> comparator)
使用自定义的对比方法
@Data
@AllArgsConstructor
public class User implements Comparable{
private String name;
private int age;
/**
* 返回0,代表等于
* 返回负数,代表当前对象小于传入对象
* 返回正数,代表当前对象大于传入对象
* @param o
* @return
*/
@Override
public int compareTo(Object o) {
return getAge()-((User)o).getAge();
}
}
//使用
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//升序(如果想要降序,将上面compareTo对比换个位置即可)
List<User> collect = users.stream()
.sorted()
.collect(Collectors.toList());
//使用自定义的排序方法
List<User> collect1 = users.stream()
.sorted((u1, u2) -> u1.getAge() - u2.getAge())
.collect(Collectors.toList());
//使用Comparator里面的方法
List<User> collect2 = users.stream()
.sorted(Comparator.comparingInt(User::getAge))
.collect(Collectors.toList());
映射
map(映射)
map操作常常用来做类型转换或者对象映射处理操作,R->T。
//修改年龄
List<User> collect = users.stream()
.map(o -> {
o.setAge(55);
return o;
}).collect(Collectors.toList());
//提取名字
List<String> collect1 = users.stream()
.map(User::getName)
.collect(Collectors.toList());
flatMap(展平)
flatMap先进行Map操作,将每个元素转为流,如果在对流进行合并,所以flatMap里面的lambda函数返回值是流
//int[][]转List<List>
int[][] ints = new int[0][0];
List<Integer> collect = Arrays.stream(ints)
.flatMap(arr->Arrays.stream(arr).boxed())
.collect(Collectors.toList());
归约
reduce(汇总,归约)
reduce常常用来做求和操作,第一个参数代表了累加的初始值和累加的类型。对于一些浮点数的累加我们可以使用BigDecimal来避免数位的损失
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//对年龄求和,这里的0代表累加的初始值
Integer reduce = users.stream()
.map(User::getAge)
.reduce(0, Integer::sum);
//对于一些浮点数的累加我们可以使用BigDecimal来避免数位的损失
BigDecimal totalPrice = goods.stream()
.map(Goods::getPrice())
.reduce(BigDecimal.ZERO, BigDecimal::add);
//接口源码注释如下,可得知第一个参数的初始值
/* <pre>{@code
* T result = identity;
* for (T element : this stream)
* result = accumulator.apply(result, element)
* return result;
* }</pre>
*/
匹配
match匹配有三种:
- 某一元素匹配:
anyMatch()
- 全部匹配:
allMatch()
- 没有一个匹配:
noneMatch()
allMatch(全部匹配)
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
//是否全部人都是12岁
boolean b1 = users.stream()
.allMatch(user -> user.getAge() == 12);
noneMatch(全不匹配)
//是否没人是10岁
boolean b2 = users.stream()
.noneMatch(user -> user.getAge() == 10);
anyMatch(存在匹配)
//是否存在一个叫tom的人
boolean b3 = users.stream()
.anyMatch(user -> user.getName().equals("tom"));
收集
collect(收集)
可以通过collect进行不同的收集操作,如分组收集。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
users.add(new User("zhangsan",16));
//返回set(底层其实是HashSet)
Set<User> collect1 = users.stream().collect(Collectors.toSet());
//返回List(其实是ArrayList)
List<User> collect2 = users.stream().collect(Collectors.toList());
//返回map(底层其实是HashMap),名字的k,年龄是v
Map<String, Integer> collect3 = users.stream()
.collect(Collectors.toMap(User::getName, User::getAge));
//返回指定的collection类型,这里选择LinkedList
LinkedList<User> collect4 = users.stream()
.collect(Collectors.toCollection(LinkedList::new));
Collectors.groupingBy(分组)
利用collect()和Collectors.groupingBy()方法可以实现分组收集的功能。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12));
users.add(new User("jack",14));
users.add(new User("tom",16));
users.add(new User("zhangsan",16));
//按年龄分组,年龄是k,List是v
Map<Integer, List<User>> collect = users.stream()
.collect(Collectors.groupingBy(User::getAge));
Collectors.groupingBy(多重分组)
Collectors.groupingBy两个参数的方法可以向下分组。
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12,"男"));
users.add(new User("jack",14,"男"));
users.add(new User("tom",16,"男"));
users.add(new User("zhangsan",16,"男"));
//先按年龄分组,在按性别分组
Map<Integer, Map<String, List<User>>> collect5 = users.stream()
.collect(Collectors
.groupingBy(User::getAge, Collectors.groupingBy(User::getSex)));
源码解读
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,Collector<? super T, A, D> downstream) {
//底层使用HashMap,downstream向下传递新的Collector
return groupingBy(classifier, HashMap::new, downstream);
}
Collectors.groupingBy(分组统计)
ArrayList<User> users = new ArrayList<>();
users.add(new User("ste",12,"男"));
users.add(new User("jack",14,"男"));
users.add(new User("tom",16,"男"));
users.add(new User("zhangsan",16,"男"));
//按性别分组,然后按年龄统计
Map<String, Integer> collect = users.stream().collect(Collectors.groupingBy(User::getSex, Collectors.summingInt(User::getAge)));