一、什么是Stream?
Stream(流)是一个来自数据源的元素队列,它可以支持聚合操作。
- 数据源:流的数据来源,构造Stream对象的数据源,比如通过一个List来构造Stream对象,这个List就是数据源;
- 聚合操作:对Stream对象进行处理后使得Stream对象返回指定规则数据的操作称之为聚合操作,比如filter、map、limit、sorted等都是聚合操作。
二、Stream 聚合操作
UmsMenu是一个菜单对象,具有树形结构,对象定义如下。
public class UmsMenu implements Serializable {
private Long id;
@ApiModelProperty(value = "父级ID")
private Long parentId;
@ApiModelProperty(value = "创建时间")
private Date createTime;
@ApiModelProperty(value = "菜单名称")
private String title;
@ApiModelProperty(value = "菜单级数")
private Integer level;
@ApiModelProperty(value = "菜单排序")
private Integer sort;
@ApiModelProperty(value = "前端名称")
private String name;
@ApiModelProperty(value = "前端图标")
private String icon;
@ApiModelProperty(value = "前端隐藏")
private Integer hidden;
//省略所有getter及setter方法
}
三、Stream对象的创建
Stream对象分为两种,一种串行的流对象,一种并行的流对象。
// menuList指所有菜单列表
// 为集合创建串行流对象
Stream<UmsMenu> stream = menuList.stream();
// 为集合创建并行流对象
Stream<UmsMenu> parallelStream = menuList.parallelStream();
四、filter
对Stream中的元素进行过滤操作,当设置条件返回true时返回相应元素。
@Test
public void filterTest(){
//filter操作:获取所有一级菜单
List<UmsMenu> oneLevelList = menuList.stream()
.filter(menu -> menu.getParentId() == 0L)
.collect(Collectors.toList());
LOGGER.info("filter操作:{}",oneLevelList);
}
五、map
对Stream中的元素进行转换处理后获取,比如可以将UmsMenu对象转换成Long对象。我们经常会有这样的需求:需要把某些对象的id提取出来,然后根据这些id去查询其他对象,这时可以使用此方法。
@Test
public void mapTest(){
//map操作:获取所有菜单的id
List<Long> idList = menuList.stream()
.map(menu -> menu.getId())
.collect(Collectors.toList());
LOGGER.info("map操作:{}",idList);
}
六、limit
从Stream中获取指定数量的元素。
@Test
public void limitTest(){
//limit操作:获取前5个菜单
List<UmsMenu> firstFiveList = menuList.stream()
.limit(5)
.collect(Collectors.toList());
LOGGER.info("limit操作:{}",firstFiveList);
}
七、count
仅获取Stream中元素的个数。
@Test
public void countTest(){
//count操作:获取所有一级菜单的个数
long count = menuList.stream()
.filter(menu -> menu.getParentId() == 0L)
.count();
LOGGER.info("count操作:{}",count);
}
八、sorted
对Stream中元素按指定规则进行排序。
@Test
public void sortedTest(){
//sorted操作:将所有菜单按照sort字段进行排序
List<UmsMenu> sortedList = menuList.stream()
.sorted((menu1,menu2)->{return menu2.getSort().compareTo(menu1.getSort());})
.collect(Collectors.toList());
LOGGER.info("sorted操作:{}",sortedList);
}
九、skip
跳过指定个数的Stream中元素,获取后面的元素。
@Test
public void skipTest(){
//skip操作:跳过前5个元素,返回后面的
List<UmsMenu> skipList = menuList.stream()
.skip(5)
.collect(Collectors.toList());
LOGGER.info("skip操作:{}",skipList);
}
十、应用
我们经常会有返回树形结构数据的需求。比如这里的菜单,具有一级菜单、二级菜单、三级菜单这样的结构。如果我们要返回一个集合,包含菜单以及菜单下面嵌套的子菜单,使用Stream API可以很方便的解决这个问题。
注意:这里我们的菜单上下级之间以parentId来关联,parentId是指上一级菜单的id,顶级菜单的parentId为0。
定义包含下级菜单的对象
继承自UmsMenu对象,只增加了一个children属性,用于存储下级菜单。
/**
* @description 后台菜单节点封装
*/
@Getter
@Setter
public class UmsMenuNode extends UmsMenu {
@ApiModelProperty(value = "子级菜单")
private List<UmsMenuNode> children;
}
定义获取树形结构的方法
我们先过滤出parentId为0的顶级菜单,然后给每个顶级菜单设置其子级菜单,covertMenuNode方法的主要用途就是从所有菜单中找出相应子级菜单。
@Override
public List<UmsMenuNode> treeList() {
List<UmsMenu> menuList = menuMapper.selectByExample(new UmsMenuExample());
List<UmsMenuNode> result = menuList.stream()
.filter(menu -> menu.getParentId().equals(0L))
.map(menu -> covertMenuNode(menu, menuList)).collect(Collectors.toList());
return result;
}
为每个菜单设置子级菜单
这里我们使用filter操作来过滤出每个菜单的子级菜单,由于子级菜单下面可能还会有子级菜单,这里我们使用递归来解决。但是递归操作什么时候停止,这里把递归调用方法放到了map操作中去,当没有子级菜单时filter下的map操作便不会再执行,从而停止递归。
/**
* 将UmsMenu转化为UmsMenuNode并设置children属性
*/
private UmsMenuNode covertMenuNode(UmsMenu menu, List<UmsMenu> menuList) {
UmsMenuNode node = new UmsMenuNode();
BeanUtils.copyProperties(menu, node);
List<UmsMenuNode> children = menuList.stream()
.filter(subMenu -> subMenu.getParentId().equals(menu.getId()))
.map(subMenu -> covertMenuNode(subMenu, menuList)).collect(Collectors.toList());
node.setChildren(children);
return node;
}
用collect方法将List转成map
有时候我们需要反复对List中的对象根据id进行查询,我们可以先把该List转换为以id为key的map结构,然后再通过map.get(id)来获取对象,这样比较方便。
@Test
public void collect2mapTest(){
//collect转map操作:将菜单列表以id为key,以菜单对象为值转换成map
Map<Long, UmsMenu> menuMap = menuList.stream()
.collect(Collectors.toMap(menu -> menu.getId(), menu -> menu));
LOGGER.info("collect转map操作:{}",menuMap);
}
数学运算
public void testNumberCalculate() {
List<Integer> ids = Arrays.asList(10, 20, 30, 40, 50);
// 计算平均值
Double average = ids.stream().collect(Collectors.averagingInt(value -> value));
System.out.println("平均值:" + average);
// 数据统计信息
IntSummaryStatistics summary = ids.stream().collect(Collectors.summarizingInt(value -> value));
System.out.println("数据统计信息: " + summary);
}
生成拼接字符串
public void testCollectJoinStrings() {
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
String joinResult = ids.stream().collect(Collectors.joining(","));
System.out.println("拼接后:" + joinResult);
}
生成集合
public void testCollectStopOptions() {
List<User> ids = Arrays.asList(new User(17), new User(22), new User(23));
// collect成list
List<User> collectList = ids.stream().filter(dept -> dept.getId() > 20)
.collect(Collectors.toList());
System.out.println("collectList:" + collectList);
// collect成Set
Set<User> collectSet = ids.stream().filter(dept -> dept.getId() > 20)
.collect(Collectors.toSet());
System.out.println("collectSet:" + collectSet);
// collect成HashMap,key为id,value为Dept对象
Map<Integer, User> collectMap = ids.stream().filter(dept -> dept.getId() > 20)
.collect(Collectors.toMap(User::getId, dept -> dept));
System.out.println("collectMap:" + collectMap);
}
一旦一个Stream被执行了终止操作之后,后续便不可以再读这个流执行其他的操作了,否则会报错
public void testHandleStreamAfterClosed() {
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
Stream<String> stream = ids.stream().filter(s -> s.length() > 2);
// 统计stream操作后剩余的元素个数
System.out.println(stream.count());
System.out.println("-----下面会报错-----");
// 判断是否有元素值等于205
try {
System.out.println(stream.anyMatch("205"::equals));
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("-----上面会报错-----");
}
简单结果终止方法
public void testSimpleStopOptions() {
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
// 统计stream操作后剩余的元素个数
System.out.println(ids.stream().filter(s -> s.length() > 2).count());
// 判断是否有元素值等于205
System.out.println(ids.stream().filter(s -> s.length() > 2).anyMatch("205"::equals));
// findFirst操作
ids.stream().filter(s -> s.length() > 2)
.findFirst()
.ifPresent(s -> System.out.println("findFirst:" + s));
}
filter、sorted、distinct、limit
public void testGetTargetUsers() {
List<String> ids = Arrays.asList("205", "10", "308", "49", "627", "193", "111", "193");
// 使用流操作
List<User> results = ids.stream()
.filter(s -> s.length() > 2)
.distinct()
.map(Integer::valueOf)
.sorted(Comparator.comparingInt(o -> o))
.limit(3)
.map(id -> new User(id))
.collect(Collectors.toList());
System.out.println(results);
}
演示map的用途:一对多转换
public void stringToIntFlatmap() {
List<String> sentences = Arrays.asList("hello world", "Jia Gou Wu Dao");
// 使用流操作
List<String> results = sentences.stream()
.flatMap(sentence -> Arrays.stream(sentence.split(" ")))
.collect(Collectors.toList());
System.out.println(results);
}
演示map的用途:一对一转换
public void stringToIntMap() {
List<String> ids = Arrays.asList("205", "105", "308", "469", "627", "193", "111");
// 使用流操作
List<User> results = ids.stream()
.map(id -> {
User user = new User(Integer.valueOf(id));
return user;
})
.collect(Collectors.toList());
System.out.println(results);
}
补充:Java 8 新特性
JDK的前世今生:细数 Java5 - 15 的那些经典特性