集合在Java中使用的非常多,在对数据处理中,List几乎是最常用的API,为了更方便地使用函数式编程对List进行操作,Java8新增了stream。
Stream表示数据流,它不是数据结构,也并不保存数据,在它上面的操作也不会改变原Stream,而是新生成Stream。它提供了过滤、排序、映射、规约等多种操作。
Stream借助Lambda表达式,提高了编程效率和程序可读性。Stream的操作可以串行编写代码,在执行的时候可以并行执行以充分利用多核CPU。
Stream上的操作可以分为两类:
- 中间操作:返回值为Stream的是中间操作;
- 完结操作:返回其他类型的是完结操作。
中间操作是对原Stream进行操作,生成一个新的Stream。多个中间操作的调用通常是链式的,这个操作链形成了一个管道。中间操作是懒操作,并不是在定义的时候执行,而是在中间操作结束后,遇到完结操作时才执行,这有助于减少资源占用,提高性能。
完结操作是对stream进行操作后,返回的不再是一个stream,而是返回其他结果。
为了进行下面的测试,我们首先初始化一个List。
package javademo.lambda.list;
import java.util.List;
public class User implements Comparable<User> {
//用户ID
private Integer userId;
//用户姓名
private String name;
//性别
private Util.GENDER gender;
//年龄
private Integer age;
//多个地址
private List<Address> addresses;
//昵称
private List<String> nicknames;
//用户来源
private Integer sourceId;
//用户订单数
private Integer orderCnt;
//get
//set
@Override
public String toString() {
return String.format("id:%d,name:%s", userId, name);
}
@Override
public boolean equals(Object obj) {
String thisName = this.getName().substring(0, 1);
String objName = ((User) obj).getName().substring(0, 1);
return thisName.equals(objName);
}
@Override
public int hashCode() {
int code = this.getName().substring(0, 1).hashCode();
return code;
}
@Override
public int compareTo(User o) {
return this.age.compareTo(o.getAge());
}
}
package javademo.lambda.list;
public class Address {
private Integer addressId;
private String addName;
public Integer getAddressId() {
return addressId;
}
public void setAddressId(Integer addressId) {
this.addressId = addressId;
}
public String getAddName() {
return addName;
}
public void setAddName(String addName) {
this.addName = addName;
}
}
package javademo.lambda.list;
import java.util.ArrayList;
import java.util.List;
public class Util {
//性别枚举
enum GENDER {
Nan,
Nv
}
/*
初始化一个用户列表。
*/
public static List<User> initList() {
List<User> users = new ArrayList<>();
users.add(new User() {
{
setUserId(1);
setName("张一");
setGender(GENDER.Nan);
setAge(88);
setAddresses(new ArrayList<Address>() {{
add(new Address() {{
setAddressId(11);
setAddName("北京市");
}});
add(new Address() {{
setAddressId(12);
setAddName("上海市");
}});
add(new Address() {{
setAddressId(13);
setAddName("广州市");
}});
}});
setNicknames(new ArrayList<String>() {{
add("大狗");
add("狗1");
}});
setSourceId(1);
setOrderCnt(10);
}
});
users.add(new User() {
{
setUserId(2);
setName("张二");
setGender(GENDER.Nan);
setAge(31);
setAddresses(new ArrayList<Address>() {{
add(new Address() {{
setAddressId(21);
setAddName("北京市");
}});
add(new Address() {{
setAddressId(22);
setAddName("杭州市");
}});
}});
setNicknames(new ArrayList<String>() {{
add("大狗");
add("狗2");
}});
setSourceId(1);
setOrderCnt(20);
}
});
users.add(new User() {
{
setUserId(3);
setName("张三");
setAge(66);
setGender(GENDER.Nv);
setNicknames(new ArrayList<String>() {{
add("大狗");
add("狗3");
}});
setSourceId(2);
setOrderCnt(5);
}
});
return users;
}
}
(1) Filter
Filter是对流对象中的元素进行过滤,它是使用java.util.function.Predicate接口对每个操作进行逻辑判断,结果为True的保留下来,Filter方法的返回值是一个新的Stream对象。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.stream.Collectors;
public class FilterDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//筛选年龄大于30岁的人
long gt30 = users.stream()
.filter((u) -> u.getAge().compareTo(30) > 0)
.count();
//筛选年龄大于30,并且地址里有北京的
//第二个filter里,判断了子列表(地址列表)里的条件
long gt30AndBeijing = users.stream()
.filter((u) -> u.getAge().compareTo(30) > 0)
.filter(u -> u.getAddresses() != null
&& u.getAddresses().stream()
.filter(a -> a.getAddName().equals("北京市"))
.collect(Collectors.toList())
.size()
> 0)
.count();
System.out.println("年龄大于30的人有几个:" + gt30);
System.out.println("年龄大于30,并且地址里有北京的人有几个:" + gt30AndBeijing);
}
}
(2) Sorted
Sorted是对流对象中的元素进行排序,它是使用java.util.Comparator接口根据指定排序规则进行排序,Sorted方法的返回值是一个新的Stream对象。
举例:
package javademo.lambda.list;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class SortedDemo {
public static void main(String[] args) {
List<User> users=Util.initList();
//根据默认排序正序排序。
//使用这种排序,User类需要集成Comparable接口,并实现compareTo方法。
List<User> sorted1= users.stream()
.sorted()
.collect(Collectors.toList());
//根据默认排序,倒序排序,使用Comparator接口的reverseOrder()方法
List<User> sorted2= users.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
//用Lambda表达式定义排序方式
List<User> sorted3= users.stream()
.sorted((u1,u2)->u1.getUserId().compareTo(u2.getUserId()))
.collect(Collectors.toList());
//可以用方法引用的方式,使用Comparator.comparing()方法来定义排序依据
//reversed是指定倒序排序
List<User> sorted4= users.stream()
.sorted(Comparator.comparing(User::getUserId).reversed())
.collect(Collectors.toList());
System.out.println("over" );
}
}
package javademo.lambda.list;
import java.util.List;
//如果要使用Sorted的默认排序,需要继承Comparable接口。
public class User implements Comparable<User> {
private Integer userId;
private String name;
private Integer age;
private List<Address> addresses;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getAge() {
return age;
}
public void setAge(Integer age) {
this.age = age;
}
public List<Address> getAddresses() {
return addresses;
}
public void setAddresses(List<Address> addresses) {
this.addresses = addresses;
}
@Override
public int compareTo(User o) {
return this.age.compareTo(o.getAge());
}
}
(3) map
map方法会对列表中每个对象进行处理,根据指定规则映射成其他类型的元素。注意:stream只能遍历一次,因为遍历一次后stream就被消费掉了,可以从源头再生成一次stream。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.stream.Collectors;
public class MapDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//遍历每一个对象,转换为其他对象,返回一个新的Stream
List<String> names = users.stream()
.map(u -> "用户名:" + u.getName())
.collect(Collectors.toList());
names.stream().forEach(System.out::println);
}
}
(4) flatMap
flatMap是对Stream进行扁平化处理,它是对一Stream进行处理,输出也是一个Stream。
什么叫做扁平化? 比如下面的例子,对一个原始流Stream进行处理,流中每个User对象里都有一个nicknames对象(别名)列表,将每个User的每个别名组成一个列表,就是扁平化处理。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class FlatMapDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//对昵称进行扁平化处理
List<String> nickNames=users.stream()
.flatMap(n->n.getNicknames().stream())
.distinct()
.collect(Collectors.toList());
//对地址中的名称进行扁平化处理
List<String> citys = users.stream()
.flatMap(u -> {
if (u.getAddresses() != null) {
return u.getAddresses().stream().map(a -> a.getAddName());
} else {
return Stream.empty();
}
})
.distinct()
.collect(Collectors.toList());
//另外一种对名称进行扁平化处理方式
List<String> citys2 = users.stream()
.map(User::getAddresses)
.flatMap(f -> {
if (f != null) {
return f.stream().map(Address::getAddName);
} else {
return Stream.empty();
}
})
.distinct()
.collect(Collectors.toList());
System.out.println("over");
}
}
(5) 查找和匹配
- allMatch:是否全部匹配
- anyMatch:是否有一个元素匹配
- noneMatch:是否没有元素匹配
- findFirst:返回第一个元素。
- findAny:返回任意一个元素。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.Optional;
public class MatchDemo {
public static void main(String[] args) {
//生成一个用户列表
List<User> users=Util.initList();
//判断是否所有的用户年龄都岛屿18
boolean isGt18= users.stream().allMatch(u->u.getAge().compareTo(18)>0);
//判断是否有年龄大于60的用户
boolean isGt60=users.stream().anyMatch(u->u.getAge().compareTo(60)>0);
//判断是否没有年龄小于18岁的用户
boolean isLt18=users.stream().noneMatch(u->u.getAge().compareTo(18)<0);
//找出符合条件的第一个
Optional<User> first=users.stream()
.filter(u->u.getAge().compareTo(18)>0)
.findFirst();
//返回符合条件的任意一个
Optional<User> anyOne=users.stream()
.filter(u->u.getAge().compareTo(18)>0)
.findAny();
System.out.println("over");
}
}
(6) reduce
reduce就是对列表中所有的元素反复结合起来得到一个值,它提供一个起始值(或者拿第一个值做为起始值),然后按照指定的规则,和Stream里前面的第1个、第2个…第N个。
sum、min、max、average实际上都是特殊的reduce。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.Optional;
public class ReduceDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//求最小值
Optional<User> minAgeUser = users.stream()
.reduce((u1, u2) -> u1.getAge() < u2.getAge() ? u1 : u2);
if (minAgeUser.isPresent()) {
System.out.println(minAgeUser.get());
}
//姓名连接起来,没有初始值
Optional<String> names1 = users.stream()
.map(User::getName)
.reduce((n1, n2) -> n1 + "," + n2);
names1.ifPresent(System.out::println);
//姓名连接起来,指定初始值
String names2 = users.stream()
.map(User::getName)
.reduce("连接起来的名字:", (n1, n2) -> n1 + "," + n2);
System.out.println(names2);
}
}
(7) collect
收集器方法collect是一个规约方法,collect方法接受一个Collectors类的里的方法,将stream中的元素累积为一个结果。可以汇总为一个值、N个分组、N个分区。
汇总:
package javademo.lambda.list;
import java.util.List;
import java.util.stream.Collectors;
public class CollectDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//连接join
String names =users.stream()
.map(User::getName)
.collect(Collectors.joining(","));
//个数
Long cnt=users.stream()
.filter(u->u.getAge()>1)
.collect(Collectors.counting());
//sum
Integer sum=users.stream()
.collect(Collectors.summingInt(User::getOrderCnt));
System.out.println("over");
}
}
分组
Collectors.groupingBy()方法用来进行分组,对每个分组进行统计,还可以进行多级分组和多级分组的统计,以下是一个例子:
package javademo.lambda.list;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
public class GroupByDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//**** 1.groupingBy一个参数的重载方法
//根据用户来源进行分组
Map<Integer, List<User>> group = users.stream()
.collect(Collectors.groupingBy(User::getSourceId));
//**** 2.groupingBy两个参数的重载方法
//分组汇总,各组的订单数量和
Map<Integer, Integer> group2 = users.stream()
.collect(Collectors.groupingBy(User::getSourceId, Collectors.summingInt(User::getOrderCnt)));
//分组,取平均订单数量
Map<Integer, Double> group3 = users.stream()
.collect(Collectors.groupingBy(User::getSourceId, Collectors.averagingDouble(User::getOrderCnt)));
//分组,取订单量最大的。
Map<Integer, User> group4 = users.stream()
.collect(Collectors.groupingBy(User::getSourceId,
Collectors.collectingAndThen(Collectors.maxBy(Comparator.comparingInt(User::getOrderCnt)), Optional::get)));
//两级分组,先根据用户来源分组,再根据性别分组
Map<Integer, Map<Util.GENDER, List<User>>> group5 = users.stream()
.collect(Collectors.groupingBy(User::getSourceId,
Collectors.groupingBy(User::getGender)
));
//两级分组,先根据用户来源分组,再根据性别分组,计算每个分组的平均年龄
Map<Integer, Map<Util.GENDER, Double>> group6 = users.stream()
.collect(Collectors.groupingBy(User::getSourceId,
Collectors.groupingBy(User::getGender, Collectors.averagingDouble(User::getAge))
));
System.out.println(group);
}
}
分区
分区可以看做是分组的特殊形式,因为分区只能根据一个 Predicate 方法分为两个分区。可以进行多级分区。
举例:
package javademo.lambda.list;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class PartitionDemo {
public static void main(String[] args) {
List<User> users = Util.initList();
//根据是否有地址信息进行分区,只能分为两个区
Map<Boolean, List<User>> part1 = users.stream()
.collect(Collectors.partitioningBy(u -> u.getAddresses() != null));
//两级分区,先根据是否有地址分区,然后再根据年龄是否大于50分区。
Map<Boolean, Map<Boolean, List<User>>> part2 = users.stream()
.collect(Collectors.partitioningBy(u -> u.getAddresses() != null,
Collectors.partitioningBy(a -> a.getAge().compareTo(50) > 0)
));
//分区下再分组,先根据是否有地址分区,再根据用户来源分组
Map<Boolean,Map<Integer,List<User>>> part3= users.stream()
.collect(Collectors.partitioningBy(u->u.getAddresses()!=null,
Collectors.groupingBy(User::getSourceId)
));
System.out.println("over");
}
}
其他操作
- distinct:去重复,默认是根据元素对象的equals方法和hashCode方法进行去重,所以如果要自定义去重逻辑,需要重新这两个方法。
- skip:跳过前N个。
- limit:截取N个。